| Message ID | 20251030165816.1095180-3-barnabas.pocze@ideasonboard.com |
|---|---|
| State | New |
| Headers | show |
| Series |
|
| Related | show |
Quoting Barnabás Pőcze (2025-10-30 16:57:56) > Add `ControlValueView`, which is a non-owning read-only handle to a control > value with a very similar interface. > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> > --- > changes in v3: > * reword some documentation > > changes in v2: > * rewrite `ControlValue::toString()` in terms of `operator<<(std::ostream&, const ControlValueView&) > to avoid duplication > --- > include/libcamera/controls.h | 68 +++++++++ > src/libcamera/controls.cpp | 268 +++++++++++++++++++++++++---------- > 2 files changed, 258 insertions(+), 78 deletions(-) > > diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h > index 1ee1971c4d..4af52b4f7d 100644 > --- a/include/libcamera/controls.h > +++ b/include/libcamera/controls.h > @@ -8,6 +8,7 @@ > #pragma once > > #include <assert.h> > +#include <cstddef> > #include <map> > #include <optional> > #include <stdint.h> > @@ -25,6 +26,7 @@ > namespace libcamera { > > class ControlValidator; > +class ControlValueView; > > enum ControlType { > ControlTypeNone, > @@ -160,6 +162,8 @@ public: > value.data(), value.size(), sizeof(typename T::value_type)); > } > > + explicit ControlValue(const ControlValueView &cvv); > + > ~ControlValue(); > > ControlValue(const ControlValue &other); > @@ -247,6 +251,70 @@ private: > std::size_t numElements, std::size_t elementSize); > }; > > +class ControlValueView > +{ > +public: > + constexpr ControlValueView() noexcept > + : type_(ControlTypeNone) > + { > + } > + > + ControlValueView(const ControlValue &cv) noexcept > + : ControlValueView(cv.type(), cv.isArray(), cv.numElements(), > + reinterpret_cast<const std::byte *>(cv.data().data())) > + { > + } > + > +#ifndef __DOXYGEN__ > + // TODO: should have restricted access? Restricted access to what ? Do you mean doxygen should? or this constructor should be private? I think this is an internal detail of just how ControlValueView(const ControlValue &cv) is constructed right? So can it does look like it could be a private constructor...? As this is public API I think keeping it minimal makes sense. It's easier to add than subtract :D > + ControlValueView(ControlType type, bool isArray, std::size_t numElements, > + const std::byte *data) noexcept > + : type_(type), isArray_(isArray), numElements_(numElements), > + data_(data) > + { > + assert(isArray || numElements == 1); > + } > +#endif > + > + [[nodiscard]] explicit operator bool() const { return type_ != ControlTypeNone; } > + [[nodiscard]] ControlType type() const { return type_; } > + [[nodiscard]] bool isNone() const { return type_ == ControlTypeNone; } > + [[nodiscard]] bool isArray() const { return isArray_; } > + [[nodiscard]] std::size_t numElements() const { return numElements_; } > + [[nodiscard]] Span<const std::byte> data() const; > + > + [[nodiscard]] bool operator==(const ControlValueView &other) const; > + > + [[nodiscard]] bool operator!=(const ControlValueView &other) const > + { > + return !(*this == other); > + } > + > + template<typename T> > + [[nodiscard]] auto get() const > + { > + using TypeInfo = details::control_type<std::remove_cv_t<T>>; > + > + assert(type_ == TypeInfo::value); > + assert(isArray_ == (TypeInfo::size > 0)); > + > + if constexpr (TypeInfo::size > 0) { > + return T(reinterpret_cast<const typename T::value_type *>(data().data()), numElements_); > + } else { > + assert(numElements_ == 1); > + return *reinterpret_cast<const T *>(data().data()); > + } > + } > + > +private: > + ControlType type_ : 8; > + bool isArray_ = false; > + uint32_t numElements_ = 0; > + const std::byte *data_ = nullptr; > +}; > + > +std::ostream &operator<<(std::ostream &s, const ControlValueView &v); > + > class ControlId > { > public: > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp > index 1e1b49e6bd..222030c434 100644 > --- a/src/libcamera/controls.cpp > +++ b/src/libcamera/controls.cpp > @@ -106,6 +106,16 @@ ControlValue::ControlValue() > { > } > > +/** > + * \brief Construct a ControlValue from a ControlValueView > + */ > +ControlValue::ControlValue(const ControlValueView &cvv) > + : ControlValue() > +{ > + set(cvv.type(), cvv.isArray(), cvv.data().data(), > + cvv.numElements(), ControlValueSize[cvv.type()]); > +} > + > /** > * \fn template<typename T> T ControlValue::ControlValue(const T &value) > * \brief Construct a ControlValue of type T > @@ -213,84 +223,7 @@ Span<uint8_t> ControlValue::data() > */ > std::string ControlValue::toString() const > { > - if (type_ == ControlTypeNone) > - return "<ValueType Error>"; > - > - const uint8_t *data = ControlValue::data().data(); > - > - if (type_ == ControlTypeString) > - return std::string(reinterpret_cast<const char *>(data), > - numElements_); > - > - std::string str(isArray_ ? "[ " : ""); > - > - for (unsigned int i = 0; i < numElements_; ++i) { > - switch (type_) { > - case ControlTypeBool: { > - const bool *value = reinterpret_cast<const bool *>(data); > - str += *value ? "true" : "false"; > - break; > - } > - case ControlTypeByte: { > - const uint8_t *value = reinterpret_cast<const uint8_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeUnsigned16: { > - const uint16_t *value = reinterpret_cast<const uint16_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeUnsigned32: { > - const uint32_t *value = reinterpret_cast<const uint32_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeInteger32: { > - const int32_t *value = reinterpret_cast<const int32_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeInteger64: { > - const int64_t *value = reinterpret_cast<const int64_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeFloat: { > - const float *value = reinterpret_cast<const float *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeRectangle: { > - const Rectangle *value = reinterpret_cast<const Rectangle *>(data); > - str += value->toString(); > - break; > - } > - case ControlTypeSize: { > - const Size *value = reinterpret_cast<const Size *>(data); > - str += value->toString(); > - break; > - } > - case ControlTypePoint: { > - const Point *value = reinterpret_cast<const Point *>(data); > - str += value->toString(); > - break; > - } > - case ControlTypeNone: > - case ControlTypeString: > - break; > - } > - > - if (i + 1 != numElements_) > - str += ", "; > - > - data += ControlValueSize[type_]; > - } > - > - if (isArray_) > - str += " ]"; > - > - return str; > + return static_cast<std::ostringstream&&>(std::ostringstream{} << ControlValueView(*this)).str(); > } > > /** > @@ -395,6 +328,185 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen > storage_ = reinterpret_cast<void *>(new uint8_t[newSize]); > } > > +/** > + * \fn ControlValueView::ControlValueView() > + * \brief Construct an empty view > + * \sa ControlValue::ControlValue() > + */ > + > +/** > + * \fn ControlValueView::ControlValueView(const ControlValue &v) > + * \brief Construct a view referring to \a v > + * > + * The constructed view will refer to the value stored by \a v, and thus > + * \a v must not be modified or destroyed before the view is destroyed. > + * > + * \sa ControlValue::ControlValue() > + */ > + > +/** > + * \fn ControlValueView::operator bool() const > + * \brief Determine if the referenced ControlValue is valid > + * \sa ControlValueView::isNone() > + */ > + > +/** > + * \fn ControlType ControlValueView::type() const > + * \copydoc ControlValue::type() > + * \sa ControlValue::type() > + */ > + > +/** > + * \fn ControlValueView::isNone() const > + * \copydoc ControlValue::isNone() > + * \sa ControlValue::isNone() > + */ > + > +/** > + * \fn ControlValueView::isArray() const > + * \copydoc ControlValue::isArray() > + * \sa ControlValue::isArray() > + */ > + > +/** > + * \fn ControlValueView::numElements() const > + * \copydoc ControlValue::numElements() > + * \sa ControlValue::numElements() > + */ > + > +/** > + * \copydoc ControlValue::data() > + * \sa ControlValue::data() > + */ > +Span<const std::byte> ControlValueView::data() const > +{ > + return { data_, numElements_ * ControlValueSize[type_] }; > +} > + > +/** > + * \copydoc ControlValue::operator==() > + * \sa ControlValue::operator==() > + * \sa ControlValueView::operator!=() > + */ > +bool ControlValueView::operator==(const ControlValueView &other) const > +{ > + if (type_ != other.type_) > + return false; > + > + if (numElements_ != other.numElements_) > + return false; > + > + if (isArray_ != other.isArray_) > + return false; > + > + const auto d = data(); > + > + return memcmp(d.data(), other.data_, d.size_bytes()) == 0; > +} > + > +/** > + * \fn ControlValueView::operator!=() const > + * \copydoc ControlValue::operator!=() > + * \sa ControlValue::operator!=() > + * \sa ControlValueView::operator==() > + */ > + > +/** > + * \fn template<typename T> T ControlValueView::get() const > + * \copydoc ControlValue::get() > + * \sa ControlValue::get() > + */ > + > +/** > + * \brief Insert a text representation of a value into an output stream > + * \sa ControlValue::toString() > + */ > +std::ostream &operator<<(std::ostream &s, const ControlValueView &v) > +{ > + const auto type = v.type(); > + if (type == ControlTypeNone) > + return s << "None"; > + > + const auto *data = v.data().data(); > + const auto numElements = v.numElements(); > + > + if (type == ControlTypeString) > + return s << std::string_view(reinterpret_cast<const char *>(data), > + numElements); > + > + const bool isArray = v.isArray(); > + if (isArray) > + s << "[ "; > + > + for (std::size_t i = 0; i < numElements; ++i) { > + if (i > 0) > + s << ", "; > + > + switch (type) { > + case ControlTypeBool: { > + const bool *value = reinterpret_cast<const bool *>(data); > + s << (*value ? "true" : "false"); > + break; > + } > + case ControlTypeByte: { > + const auto *value = reinterpret_cast<const uint8_t *>(data); > + s << static_cast<unsigned int>(*value); > + break; > + } > + case ControlTypeUnsigned16: { > + const auto *value = reinterpret_cast<const uint16_t *>(data); > + s << *value; > + break; > + } > + case ControlTypeUnsigned32: { > + const auto *value = reinterpret_cast<const uint32_t *>(data); > + s << *value; > + break; > + } > + case ControlTypeInteger32: { > + const auto *value = reinterpret_cast<const int32_t *>(data); > + s << *value; > + break; > + } > + case ControlTypeInteger64: { > + const auto *value = reinterpret_cast<const int64_t *>(data); > + s << *value; > + break; > + } > + case ControlTypeFloat: { > + const auto *value = reinterpret_cast<const float *>(data); > + s << std::fixed << *value; > + break; > + } > + case ControlTypeRectangle: { > + const auto *value = reinterpret_cast<const Rectangle *>(data); > + s << *value; > + break; > + } > + case ControlTypeSize: { > + const auto *value = reinterpret_cast<const Size *>(data); > + s << *value; > + break; > + } > + case ControlTypePoint: { > + const auto *value = reinterpret_cast<const Point *>(data); > + s << *value; > + break; > + } > + case ControlTypeNone: > + case ControlTypeString: haha I was about to say what about strings - but it's handled early on so: Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > + break; > + } > + > + data += ControlValueSize[type]; > + } > + > + if (isArray) > + s << " ]"; > + > + return s; > +} > + > /** > * \class ControlId > * \brief Control static metadata > -- > 2.51.1 >
Hi Barnabas On 30/10/2025 16:57, Barnabás Pőcze wrote: > Add `ControlValueView`, which is a non-owning read-only handle to a control > value with a very similar interface. > > Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> > Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> > --- I think the code here is fine, but I don't quite understand the need for the new class over just using ControlValue, what's it going to be used for? > changes in v3: > * reword some documentation > > changes in v2: > * rewrite `ControlValue::toString()` in terms of `operator<<(std::ostream&, const ControlValueView&) > to avoid duplication > --- > include/libcamera/controls.h | 68 +++++++++ > src/libcamera/controls.cpp | 268 +++++++++++++++++++++++++---------- > 2 files changed, 258 insertions(+), 78 deletions(-) > > diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h > index 1ee1971c4d..4af52b4f7d 100644 > --- a/include/libcamera/controls.h > +++ b/include/libcamera/controls.h > @@ -8,6 +8,7 @@ > #pragma once > > #include <assert.h> > +#include <cstddef> > #include <map> > #include <optional> > #include <stdint.h> > @@ -25,6 +26,7 @@ > namespace libcamera { > > class ControlValidator; > +class ControlValueView; > > enum ControlType { > ControlTypeNone, > @@ -160,6 +162,8 @@ public: > value.data(), value.size(), sizeof(typename T::value_type)); > } > > + explicit ControlValue(const ControlValueView &cvv); > + > ~ControlValue(); > > ControlValue(const ControlValue &other); > @@ -247,6 +251,70 @@ private: > std::size_t numElements, std::size_t elementSize); > }; > > +class ControlValueView > +{ > +public: > + constexpr ControlValueView() noexcept > + : type_(ControlTypeNone) > + { > + } > + > + ControlValueView(const ControlValue &cv) noexcept > + : ControlValueView(cv.type(), cv.isArray(), cv.numElements(), > + reinterpret_cast<const std::byte *>(cv.data().data())) > + { > + } > + > +#ifndef __DOXYGEN__ > + // TODO: should have restricted access? > + ControlValueView(ControlType type, bool isArray, std::size_t numElements, > + const std::byte *data) noexcept > + : type_(type), isArray_(isArray), numElements_(numElements), > + data_(data) > + { > + assert(isArray || numElements == 1); > + } > +#endif > + > + [[nodiscard]] explicit operator bool() const { return type_ != ControlTypeNone; } > + [[nodiscard]] ControlType type() const { return type_; } > + [[nodiscard]] bool isNone() const { return type_ == ControlTypeNone; } > + [[nodiscard]] bool isArray() const { return isArray_; } > + [[nodiscard]] std::size_t numElements() const { return numElements_; } > + [[nodiscard]] Span<const std::byte> data() const; > + > + [[nodiscard]] bool operator==(const ControlValueView &other) const; > + > + [[nodiscard]] bool operator!=(const ControlValueView &other) const > + { > + return !(*this == other); > + } > + > + template<typename T> > + [[nodiscard]] auto get() const > + { > + using TypeInfo = details::control_type<std::remove_cv_t<T>>; > + > + assert(type_ == TypeInfo::value); > + assert(isArray_ == (TypeInfo::size > 0)); > + > + if constexpr (TypeInfo::size > 0) { > + return T(reinterpret_cast<const typename T::value_type *>(data().data()), numElements_); > + } else { > + assert(numElements_ == 1); > + return *reinterpret_cast<const T *>(data().data()); > + } > + } > + > +private: > + ControlType type_ : 8; > + bool isArray_ = false; > + uint32_t numElements_ = 0; > + const std::byte *data_ = nullptr; > +}; > + > +std::ostream &operator<<(std::ostream &s, const ControlValueView &v); > + > class ControlId > { > public: > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp > index 1e1b49e6bd..222030c434 100644 > --- a/src/libcamera/controls.cpp > +++ b/src/libcamera/controls.cpp > @@ -106,6 +106,16 @@ ControlValue::ControlValue() > { > } > > +/** > + * \brief Construct a ControlValue from a ControlValueView > + */ > +ControlValue::ControlValue(const ControlValueView &cvv) > + : ControlValue() > +{ > + set(cvv.type(), cvv.isArray(), cvv.data().data(), > + cvv.numElements(), ControlValueSize[cvv.type()]); > +} > + > /** > * \fn template<typename T> T ControlValue::ControlValue(const T &value) > * \brief Construct a ControlValue of type T > @@ -213,84 +223,7 @@ Span<uint8_t> ControlValue::data() > */ > std::string ControlValue::toString() const > { > - if (type_ == ControlTypeNone) > - return "<ValueType Error>"; > - > - const uint8_t *data = ControlValue::data().data(); > - > - if (type_ == ControlTypeString) > - return std::string(reinterpret_cast<const char *>(data), > - numElements_); > - > - std::string str(isArray_ ? "[ " : ""); > - > - for (unsigned int i = 0; i < numElements_; ++i) { > - switch (type_) { > - case ControlTypeBool: { > - const bool *value = reinterpret_cast<const bool *>(data); > - str += *value ? "true" : "false"; > - break; > - } > - case ControlTypeByte: { > - const uint8_t *value = reinterpret_cast<const uint8_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeUnsigned16: { > - const uint16_t *value = reinterpret_cast<const uint16_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeUnsigned32: { > - const uint32_t *value = reinterpret_cast<const uint32_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeInteger32: { > - const int32_t *value = reinterpret_cast<const int32_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeInteger64: { > - const int64_t *value = reinterpret_cast<const int64_t *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeFloat: { > - const float *value = reinterpret_cast<const float *>(data); > - str += std::to_string(*value); > - break; > - } > - case ControlTypeRectangle: { > - const Rectangle *value = reinterpret_cast<const Rectangle *>(data); > - str += value->toString(); > - break; > - } > - case ControlTypeSize: { > - const Size *value = reinterpret_cast<const Size *>(data); > - str += value->toString(); > - break; > - } > - case ControlTypePoint: { > - const Point *value = reinterpret_cast<const Point *>(data); > - str += value->toString(); > - break; > - } > - case ControlTypeNone: > - case ControlTypeString: > - break; > - } > - > - if (i + 1 != numElements_) > - str += ", "; > - > - data += ControlValueSize[type_]; > - } > - > - if (isArray_) > - str += " ]"; > - > - return str; > + return static_cast<std::ostringstream&&>(std::ostringstream{} << ControlValueView(*this)).str(); > } > > /** > @@ -395,6 +328,185 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen > storage_ = reinterpret_cast<void *>(new uint8_t[newSize]); > } > > +/** > + * \fn ControlValueView::ControlValueView() > + * \brief Construct an empty view > + * \sa ControlValue::ControlValue() > + */ > + > +/** > + * \fn ControlValueView::ControlValueView(const ControlValue &v) > + * \brief Construct a view referring to \a v > + * > + * The constructed view will refer to the value stored by \a v, and thus > + * \a v must not be modified or destroyed before the view is destroyed. > + * > + * \sa ControlValue::ControlValue() > + */ > + > +/** > + * \fn ControlValueView::operator bool() const > + * \brief Determine if the referenced ControlValue is valid > + * \sa ControlValueView::isNone() > + */ > + > +/** > + * \fn ControlType ControlValueView::type() const > + * \copydoc ControlValue::type() > + * \sa ControlValue::type() > + */ > + > +/** > + * \fn ControlValueView::isNone() const > + * \copydoc ControlValue::isNone() > + * \sa ControlValue::isNone() > + */ > + > +/** > + * \fn ControlValueView::isArray() const > + * \copydoc ControlValue::isArray() > + * \sa ControlValue::isArray() > + */ > + > +/** > + * \fn ControlValueView::numElements() const > + * \copydoc ControlValue::numElements() > + * \sa ControlValue::numElements() > + */ > + > +/** > + * \copydoc ControlValue::data() > + * \sa ControlValue::data() > + */ > +Span<const std::byte> ControlValueView::data() const > +{ > + return { data_, numElements_ * ControlValueSize[type_] }; > +} > + > +/** > + * \copydoc ControlValue::operator==() > + * \sa ControlValue::operator==() > + * \sa ControlValueView::operator!=() > + */ > +bool ControlValueView::operator==(const ControlValueView &other) const > +{ > + if (type_ != other.type_) > + return false; > + > + if (numElements_ != other.numElements_) > + return false; > + > + if (isArray_ != other.isArray_) > + return false; > + > + const auto d = data(); > + > + return memcmp(d.data(), other.data_, d.size_bytes()) == 0; > +} > + > +/** > + * \fn ControlValueView::operator!=() const > + * \copydoc ControlValue::operator!=() > + * \sa ControlValue::operator!=() > + * \sa ControlValueView::operator==() > + */ > + > +/** > + * \fn template<typename T> T ControlValueView::get() const > + * \copydoc ControlValue::get() > + * \sa ControlValue::get() > + */ > + > +/** > + * \brief Insert a text representation of a value into an output stream > + * \sa ControlValue::toString() > + */ > +std::ostream &operator<<(std::ostream &s, const ControlValueView &v) > +{ > + const auto type = v.type(); > + if (type == ControlTypeNone) > + return s << "None"; > + > + const auto *data = v.data().data(); > + const auto numElements = v.numElements(); > + > + if (type == ControlTypeString) > + return s << std::string_view(reinterpret_cast<const char *>(data), > + numElements); > + > + const bool isArray = v.isArray(); > + if (isArray) > + s << "[ "; > + > + for (std::size_t i = 0; i < numElements; ++i) { > + if (i > 0) > + s << ", "; > + > + switch (type) { > + case ControlTypeBool: { > + const bool *value = reinterpret_cast<const bool *>(data); > + s << (*value ? "true" : "false"); > + break; > + } > + case ControlTypeByte: { > + const auto *value = reinterpret_cast<const uint8_t *>(data); > + s << static_cast<unsigned int>(*value); > + break; > + } > + case ControlTypeUnsigned16: { > + const auto *value = reinterpret_cast<const uint16_t *>(data); > + s << *value; > + break; > + } > + case ControlTypeUnsigned32: { > + const auto *value = reinterpret_cast<const uint32_t *>(data); > + s << *value; > + break; > + } > + case ControlTypeInteger32: { > + const auto *value = reinterpret_cast<const int32_t *>(data); > + s << *value; > + break; > + } > + case ControlTypeInteger64: { > + const auto *value = reinterpret_cast<const int64_t *>(data); > + s << *value; > + break; > + } > + case ControlTypeFloat: { > + const auto *value = reinterpret_cast<const float *>(data); > + s << std::fixed << *value; > + break; > + } > + case ControlTypeRectangle: { > + const auto *value = reinterpret_cast<const Rectangle *>(data); > + s << *value; > + break; > + } > + case ControlTypeSize: { > + const auto *value = reinterpret_cast<const Size *>(data); > + s << *value; > + break; > + } > + case ControlTypePoint: { > + const auto *value = reinterpret_cast<const Point *>(data); > + s << *value; > + break; > + } > + case ControlTypeNone: > + case ControlTypeString: > + break; > + } > + > + data += ControlValueSize[type]; > + } > + > + if (isArray) > + s << " ]"; > + > + return s; > +} > + > /** > * \class ControlId > * \brief Control static metadata
Hi 2025. 11. 02. 9:16 keltezéssel, Dan Scally írta: > Hi Barnabas > > On 30/10/2025 16:57, Barnabás Pőcze wrote: >> Add `ControlValueView`, which is a non-owning read-only handle to a control >> value with a very similar interface. >> >> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> >> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> >> --- > > I think the code here is fine, but I don't quite understand the need for the new class over just using ControlValue, what's it going to be used for? The idea is that MetadataList should manage the storage for each value in it, which conflicts with ControlValue, which itself also manages the storage for the contained value. So ControlValueView is added to separate read-only interface of ControlValue from the other parts, so that it can be used in situations where the underlying storage is managed externally. I believe `ControlValue` could technically be reused in this specific instance, but it was not explored in depth. > >> changes in v3: >> * reword some documentation >> >> changes in v2: >> * rewrite `ControlValue::toString()` in terms of `operator<<(std::ostream&, const ControlValueView&) >> to avoid duplication >> --- >> include/libcamera/controls.h | 68 +++++++++ >> src/libcamera/controls.cpp | 268 +++++++++++++++++++++++++---------- >> 2 files changed, 258 insertions(+), 78 deletions(-) >> >> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h >> index 1ee1971c4d..4af52b4f7d 100644 >> --- a/include/libcamera/controls.h >> +++ b/include/libcamera/controls.h >> @@ -8,6 +8,7 @@ >> #pragma once >> #include <assert.h> >> +#include <cstddef> >> #include <map> >> #include <optional> >> #include <stdint.h> >> @@ -25,6 +26,7 @@ >> namespace libcamera { >> class ControlValidator; >> +class ControlValueView; >> enum ControlType { >> ControlTypeNone, >> @@ -160,6 +162,8 @@ public: >> value.data(), value.size(), sizeof(typename T::value_type)); >> } >> + explicit ControlValue(const ControlValueView &cvv); >> + >> ~ControlValue(); >> ControlValue(const ControlValue &other); >> @@ -247,6 +251,70 @@ private: >> std::size_t numElements, std::size_t elementSize); >> }; >> +class ControlValueView >> +{ >> +public: >> + constexpr ControlValueView() noexcept >> + : type_(ControlTypeNone) >> + { >> + } >> + >> + ControlValueView(const ControlValue &cv) noexcept >> + : ControlValueView(cv.type(), cv.isArray(), cv.numElements(), >> + reinterpret_cast<const std::byte *>(cv.data().data())) >> + { >> + } >> + >> +#ifndef __DOXYGEN__ >> + // TODO: should have restricted access? >> + ControlValueView(ControlType type, bool isArray, std::size_t numElements, >> + const std::byte *data) noexcept >> + : type_(type), isArray_(isArray), numElements_(numElements), >> + data_(data) >> + { >> + assert(isArray || numElements == 1); >> + } >> +#endif >> + >> + [[nodiscard]] explicit operator bool() const { return type_ != ControlTypeNone; } >> + [[nodiscard]] ControlType type() const { return type_; } >> + [[nodiscard]] bool isNone() const { return type_ == ControlTypeNone; } >> + [[nodiscard]] bool isArray() const { return isArray_; } >> + [[nodiscard]] std::size_t numElements() const { return numElements_; } >> + [[nodiscard]] Span<const std::byte> data() const; >> + >> + [[nodiscard]] bool operator==(const ControlValueView &other) const; >> + >> + [[nodiscard]] bool operator!=(const ControlValueView &other) const >> + { >> + return !(*this == other); >> + } >> + >> + template<typename T> >> + [[nodiscard]] auto get() const >> + { >> + using TypeInfo = details::control_type<std::remove_cv_t<T>>; >> + >> + assert(type_ == TypeInfo::value); >> + assert(isArray_ == (TypeInfo::size > 0)); >> + >> + if constexpr (TypeInfo::size > 0) { >> + return T(reinterpret_cast<const typename T::value_type *>(data().data()), numElements_); >> + } else { >> + assert(numElements_ == 1); >> + return *reinterpret_cast<const T *>(data().data()); >> + } >> + } >> + >> +private: >> + ControlType type_ : 8; >> + bool isArray_ = false; >> + uint32_t numElements_ = 0; >> + const std::byte *data_ = nullptr; >> +}; >> + >> +std::ostream &operator<<(std::ostream &s, const ControlValueView &v); >> + >> class ControlId >> { >> public: >> diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp >> index 1e1b49e6bd..222030c434 100644 >> --- a/src/libcamera/controls.cpp >> +++ b/src/libcamera/controls.cpp >> @@ -106,6 +106,16 @@ ControlValue::ControlValue() >> { >> } >> +/** >> + * \brief Construct a ControlValue from a ControlValueView >> + */ >> +ControlValue::ControlValue(const ControlValueView &cvv) >> + : ControlValue() >> +{ >> + set(cvv.type(), cvv.isArray(), cvv.data().data(), >> + cvv.numElements(), ControlValueSize[cvv.type()]); >> +} >> + >> /** >> * \fn template<typename T> T ControlValue::ControlValue(const T &value) >> * \brief Construct a ControlValue of type T >> @@ -213,84 +223,7 @@ Span<uint8_t> ControlValue::data() >> */ >> std::string ControlValue::toString() const >> { >> - if (type_ == ControlTypeNone) >> - return "<ValueType Error>"; >> - >> - const uint8_t *data = ControlValue::data().data(); >> - >> - if (type_ == ControlTypeString) >> - return std::string(reinterpret_cast<const char *>(data), >> - numElements_); >> - >> - std::string str(isArray_ ? "[ " : ""); >> - >> - for (unsigned int i = 0; i < numElements_; ++i) { >> - switch (type_) { >> - case ControlTypeBool: { >> - const bool *value = reinterpret_cast<const bool *>(data); >> - str += *value ? "true" : "false"; >> - break; >> - } >> - case ControlTypeByte: { >> - const uint8_t *value = reinterpret_cast<const uint8_t *>(data); >> - str += std::to_string(*value); >> - break; >> - } >> - case ControlTypeUnsigned16: { >> - const uint16_t *value = reinterpret_cast<const uint16_t *>(data); >> - str += std::to_string(*value); >> - break; >> - } >> - case ControlTypeUnsigned32: { >> - const uint32_t *value = reinterpret_cast<const uint32_t *>(data); >> - str += std::to_string(*value); >> - break; >> - } >> - case ControlTypeInteger32: { >> - const int32_t *value = reinterpret_cast<const int32_t *>(data); >> - str += std::to_string(*value); >> - break; >> - } >> - case ControlTypeInteger64: { >> - const int64_t *value = reinterpret_cast<const int64_t *>(data); >> - str += std::to_string(*value); >> - break; >> - } >> - case ControlTypeFloat: { >> - const float *value = reinterpret_cast<const float *>(data); >> - str += std::to_string(*value); >> - break; >> - } >> - case ControlTypeRectangle: { >> - const Rectangle *value = reinterpret_cast<const Rectangle *>(data); >> - str += value->toString(); >> - break; >> - } >> - case ControlTypeSize: { >> - const Size *value = reinterpret_cast<const Size *>(data); >> - str += value->toString(); >> - break; >> - } >> - case ControlTypePoint: { >> - const Point *value = reinterpret_cast<const Point *>(data); >> - str += value->toString(); >> - break; >> - } >> - case ControlTypeNone: >> - case ControlTypeString: >> - break; >> - } >> - >> - if (i + 1 != numElements_) >> - str += ", "; >> - >> - data += ControlValueSize[type_]; >> - } >> - >> - if (isArray_) >> - str += " ]"; >> - >> - return str; >> + return static_cast<std::ostringstream&&>(std::ostringstream{} << ControlValueView(*this)).str(); >> } >> /** >> @@ -395,6 +328,185 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen >> storage_ = reinterpret_cast<void *>(new uint8_t[newSize]); >> } >> +/** >> + * \fn ControlValueView::ControlValueView() >> + * \brief Construct an empty view >> + * \sa ControlValue::ControlValue() >> + */ >> + >> +/** >> + * \fn ControlValueView::ControlValueView(const ControlValue &v) >> + * \brief Construct a view referring to \a v >> + * >> + * The constructed view will refer to the value stored by \a v, and thus >> + * \a v must not be modified or destroyed before the view is destroyed. >> + * >> + * \sa ControlValue::ControlValue() >> + */ >> + >> +/** >> + * \fn ControlValueView::operator bool() const >> + * \brief Determine if the referenced ControlValue is valid >> + * \sa ControlValueView::isNone() >> + */ >> + >> +/** >> + * \fn ControlType ControlValueView::type() const >> + * \copydoc ControlValue::type() >> + * \sa ControlValue::type() >> + */ >> + >> +/** >> + * \fn ControlValueView::isNone() const >> + * \copydoc ControlValue::isNone() >> + * \sa ControlValue::isNone() >> + */ >> + >> +/** >> + * \fn ControlValueView::isArray() const >> + * \copydoc ControlValue::isArray() >> + * \sa ControlValue::isArray() >> + */ >> + >> +/** >> + * \fn ControlValueView::numElements() const >> + * \copydoc ControlValue::numElements() >> + * \sa ControlValue::numElements() >> + */ >> + >> +/** >> + * \copydoc ControlValue::data() >> + * \sa ControlValue::data() >> + */ >> +Span<const std::byte> ControlValueView::data() const >> +{ >> + return { data_, numElements_ * ControlValueSize[type_] }; >> +} >> + >> +/** >> + * \copydoc ControlValue::operator==() >> + * \sa ControlValue::operator==() >> + * \sa ControlValueView::operator!=() >> + */ >> +bool ControlValueView::operator==(const ControlValueView &other) const >> +{ >> + if (type_ != other.type_) >> + return false; >> + >> + if (numElements_ != other.numElements_) >> + return false; >> + >> + if (isArray_ != other.isArray_) >> + return false; >> + >> + const auto d = data(); >> + >> + return memcmp(d.data(), other.data_, d.size_bytes()) == 0; >> +} >> + >> +/** >> + * \fn ControlValueView::operator!=() const >> + * \copydoc ControlValue::operator!=() >> + * \sa ControlValue::operator!=() >> + * \sa ControlValueView::operator==() >> + */ >> + >> +/** >> + * \fn template<typename T> T ControlValueView::get() const >> + * \copydoc ControlValue::get() >> + * \sa ControlValue::get() >> + */ >> + >> +/** >> + * \brief Insert a text representation of a value into an output stream >> + * \sa ControlValue::toString() >> + */ >> +std::ostream &operator<<(std::ostream &s, const ControlValueView &v) >> +{ >> + const auto type = v.type(); >> + if (type == ControlTypeNone) >> + return s << "None"; >> + >> + const auto *data = v.data().data(); >> + const auto numElements = v.numElements(); >> + >> + if (type == ControlTypeString) >> + return s << std::string_view(reinterpret_cast<const char *>(data), >> + numElements); >> + >> + const bool isArray = v.isArray(); >> + if (isArray) >> + s << "[ "; >> + >> + for (std::size_t i = 0; i < numElements; ++i) { >> + if (i > 0) >> + s << ", "; >> + >> + switch (type) { >> + case ControlTypeBool: { >> + const bool *value = reinterpret_cast<const bool *>(data); >> + s << (*value ? "true" : "false"); >> + break; >> + } >> + case ControlTypeByte: { >> + const auto *value = reinterpret_cast<const uint8_t *>(data); >> + s << static_cast<unsigned int>(*value); >> + break; >> + } >> + case ControlTypeUnsigned16: { >> + const auto *value = reinterpret_cast<const uint16_t *>(data); >> + s << *value; >> + break; >> + } >> + case ControlTypeUnsigned32: { >> + const auto *value = reinterpret_cast<const uint32_t *>(data); >> + s << *value; >> + break; >> + } >> + case ControlTypeInteger32: { >> + const auto *value = reinterpret_cast<const int32_t *>(data); >> + s << *value; >> + break; >> + } >> + case ControlTypeInteger64: { >> + const auto *value = reinterpret_cast<const int64_t *>(data); >> + s << *value; >> + break; >> + } >> + case ControlTypeFloat: { >> + const auto *value = reinterpret_cast<const float *>(data); >> + s << std::fixed << *value; >> + break; >> + } >> + case ControlTypeRectangle: { >> + const auto *value = reinterpret_cast<const Rectangle *>(data); >> + s << *value; >> + break; >> + } >> + case ControlTypeSize: { >> + const auto *value = reinterpret_cast<const Size *>(data); >> + s << *value; >> + break; >> + } >> + case ControlTypePoint: { >> + const auto *value = reinterpret_cast<const Point *>(data); >> + s << *value; >> + break; >> + } >> + case ControlTypeNone: >> + case ControlTypeString: >> + break; >> + } >> + >> + data += ControlValueSize[type]; >> + } >> + >> + if (isArray) >> + s << " ]"; >> + >> + return s; >> +} >> + >> /** >> * \class ControlId >> * \brief Control static metadata >
Hi 2025. 10. 31. 9:44 keltezéssel, Kieran Bingham írta: > Quoting Barnabás Pőcze (2025-10-30 16:57:56) >> Add `ControlValueView`, which is a non-owning read-only handle to a control >> value with a very similar interface. >> >> Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> >> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> >> --- >> changes in v3: >> * reword some documentation >> >> changes in v2: >> * rewrite `ControlValue::toString()` in terms of `operator<<(std::ostream&, const ControlValueView&) >> to avoid duplication >> --- >> include/libcamera/controls.h | 68 +++++++++ >> src/libcamera/controls.cpp | 268 +++++++++++++++++++++++++---------- >> 2 files changed, 258 insertions(+), 78 deletions(-) >> >> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h >> index 1ee1971c4d..4af52b4f7d 100644 >> --- a/include/libcamera/controls.h >> +++ b/include/libcamera/controls.h >> @@ -8,6 +8,7 @@ >> #pragma once >> >> #include <assert.h> >> +#include <cstddef> >> #include <map> >> #include <optional> >> #include <stdint.h> >> @@ -25,6 +26,7 @@ >> namespace libcamera { >> >> class ControlValidator; >> +class ControlValueView; >> >> enum ControlType { >> ControlTypeNone, >> @@ -160,6 +162,8 @@ public: >> value.data(), value.size(), sizeof(typename T::value_type)); >> } >> >> + explicit ControlValue(const ControlValueView &cvv); >> + >> ~ControlValue(); >> >> ControlValue(const ControlValue &other); >> @@ -247,6 +251,70 @@ private: >> std::size_t numElements, std::size_t elementSize); >> }; >> >> +class ControlValueView >> +{ >> +public: >> + constexpr ControlValueView() noexcept >> + : type_(ControlTypeNone) >> + { >> + } >> + >> + ControlValueView(const ControlValue &cv) noexcept >> + : ControlValueView(cv.type(), cv.isArray(), cv.numElements(), >> + reinterpret_cast<const std::byte *>(cv.data().data())) >> + { >> + } >> + >> +#ifndef __DOXYGEN__ >> + // TODO: should have restricted access? > > Restricted access to what ? Do you mean doxygen should? or this > constructor should be private? > > I think this is an internal detail of just how ControlValueView(const > ControlValue &cv) is constructed right? So can it does look like it > could be a private constructor...? > > As this is public API I think keeping it minimal makes sense. It's > easier to add than subtract :D It should ideally be "package private". It is only used by `MetadataList` and related types, but I don't really want to add friend delcarations. I think I'll just document it as `\internal` so it does not appear in the public documentation. > >> + ControlValueView(ControlType type, bool isArray, std::size_t numElements, >> + const std::byte *data) noexcept >> + : type_(type), isArray_(isArray), numElements_(numElements), >> + data_(data) >> + { >> + assert(isArray || numElements == 1); >> + } >> +#endif >> + > [...]
diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h index 1ee1971c4d..4af52b4f7d 100644 --- a/include/libcamera/controls.h +++ b/include/libcamera/controls.h @@ -8,6 +8,7 @@ #pragma once #include <assert.h> +#include <cstddef> #include <map> #include <optional> #include <stdint.h> @@ -25,6 +26,7 @@ namespace libcamera { class ControlValidator; +class ControlValueView; enum ControlType { ControlTypeNone, @@ -160,6 +162,8 @@ public: value.data(), value.size(), sizeof(typename T::value_type)); } + explicit ControlValue(const ControlValueView &cvv); + ~ControlValue(); ControlValue(const ControlValue &other); @@ -247,6 +251,70 @@ private: std::size_t numElements, std::size_t elementSize); }; +class ControlValueView +{ +public: + constexpr ControlValueView() noexcept + : type_(ControlTypeNone) + { + } + + ControlValueView(const ControlValue &cv) noexcept + : ControlValueView(cv.type(), cv.isArray(), cv.numElements(), + reinterpret_cast<const std::byte *>(cv.data().data())) + { + } + +#ifndef __DOXYGEN__ + // TODO: should have restricted access? + ControlValueView(ControlType type, bool isArray, std::size_t numElements, + const std::byte *data) noexcept + : type_(type), isArray_(isArray), numElements_(numElements), + data_(data) + { + assert(isArray || numElements == 1); + } +#endif + + [[nodiscard]] explicit operator bool() const { return type_ != ControlTypeNone; } + [[nodiscard]] ControlType type() const { return type_; } + [[nodiscard]] bool isNone() const { return type_ == ControlTypeNone; } + [[nodiscard]] bool isArray() const { return isArray_; } + [[nodiscard]] std::size_t numElements() const { return numElements_; } + [[nodiscard]] Span<const std::byte> data() const; + + [[nodiscard]] bool operator==(const ControlValueView &other) const; + + [[nodiscard]] bool operator!=(const ControlValueView &other) const + { + return !(*this == other); + } + + template<typename T> + [[nodiscard]] auto get() const + { + using TypeInfo = details::control_type<std::remove_cv_t<T>>; + + assert(type_ == TypeInfo::value); + assert(isArray_ == (TypeInfo::size > 0)); + + if constexpr (TypeInfo::size > 0) { + return T(reinterpret_cast<const typename T::value_type *>(data().data()), numElements_); + } else { + assert(numElements_ == 1); + return *reinterpret_cast<const T *>(data().data()); + } + } + +private: + ControlType type_ : 8; + bool isArray_ = false; + uint32_t numElements_ = 0; + const std::byte *data_ = nullptr; +}; + +std::ostream &operator<<(std::ostream &s, const ControlValueView &v); + class ControlId { public: diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp index 1e1b49e6bd..222030c434 100644 --- a/src/libcamera/controls.cpp +++ b/src/libcamera/controls.cpp @@ -106,6 +106,16 @@ ControlValue::ControlValue() { } +/** + * \brief Construct a ControlValue from a ControlValueView + */ +ControlValue::ControlValue(const ControlValueView &cvv) + : ControlValue() +{ + set(cvv.type(), cvv.isArray(), cvv.data().data(), + cvv.numElements(), ControlValueSize[cvv.type()]); +} + /** * \fn template<typename T> T ControlValue::ControlValue(const T &value) * \brief Construct a ControlValue of type T @@ -213,84 +223,7 @@ Span<uint8_t> ControlValue::data() */ std::string ControlValue::toString() const { - if (type_ == ControlTypeNone) - return "<ValueType Error>"; - - const uint8_t *data = ControlValue::data().data(); - - if (type_ == ControlTypeString) - return std::string(reinterpret_cast<const char *>(data), - numElements_); - - std::string str(isArray_ ? "[ " : ""); - - for (unsigned int i = 0; i < numElements_; ++i) { - switch (type_) { - case ControlTypeBool: { - const bool *value = reinterpret_cast<const bool *>(data); - str += *value ? "true" : "false"; - break; - } - case ControlTypeByte: { - const uint8_t *value = reinterpret_cast<const uint8_t *>(data); - str += std::to_string(*value); - break; - } - case ControlTypeUnsigned16: { - const uint16_t *value = reinterpret_cast<const uint16_t *>(data); - str += std::to_string(*value); - break; - } - case ControlTypeUnsigned32: { - const uint32_t *value = reinterpret_cast<const uint32_t *>(data); - str += std::to_string(*value); - break; - } - case ControlTypeInteger32: { - const int32_t *value = reinterpret_cast<const int32_t *>(data); - str += std::to_string(*value); - break; - } - case ControlTypeInteger64: { - const int64_t *value = reinterpret_cast<const int64_t *>(data); - str += std::to_string(*value); - break; - } - case ControlTypeFloat: { - const float *value = reinterpret_cast<const float *>(data); - str += std::to_string(*value); - break; - } - case ControlTypeRectangle: { - const Rectangle *value = reinterpret_cast<const Rectangle *>(data); - str += value->toString(); - break; - } - case ControlTypeSize: { - const Size *value = reinterpret_cast<const Size *>(data); - str += value->toString(); - break; - } - case ControlTypePoint: { - const Point *value = reinterpret_cast<const Point *>(data); - str += value->toString(); - break; - } - case ControlTypeNone: - case ControlTypeString: - break; - } - - if (i + 1 != numElements_) - str += ", "; - - data += ControlValueSize[type_]; - } - - if (isArray_) - str += " ]"; - - return str; + return static_cast<std::ostringstream&&>(std::ostringstream{} << ControlValueView(*this)).str(); } /** @@ -395,6 +328,185 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen storage_ = reinterpret_cast<void *>(new uint8_t[newSize]); } +/** + * \fn ControlValueView::ControlValueView() + * \brief Construct an empty view + * \sa ControlValue::ControlValue() + */ + +/** + * \fn ControlValueView::ControlValueView(const ControlValue &v) + * \brief Construct a view referring to \a v + * + * The constructed view will refer to the value stored by \a v, and thus + * \a v must not be modified or destroyed before the view is destroyed. + * + * \sa ControlValue::ControlValue() + */ + +/** + * \fn ControlValueView::operator bool() const + * \brief Determine if the referenced ControlValue is valid + * \sa ControlValueView::isNone() + */ + +/** + * \fn ControlType ControlValueView::type() const + * \copydoc ControlValue::type() + * \sa ControlValue::type() + */ + +/** + * \fn ControlValueView::isNone() const + * \copydoc ControlValue::isNone() + * \sa ControlValue::isNone() + */ + +/** + * \fn ControlValueView::isArray() const + * \copydoc ControlValue::isArray() + * \sa ControlValue::isArray() + */ + +/** + * \fn ControlValueView::numElements() const + * \copydoc ControlValue::numElements() + * \sa ControlValue::numElements() + */ + +/** + * \copydoc ControlValue::data() + * \sa ControlValue::data() + */ +Span<const std::byte> ControlValueView::data() const +{ + return { data_, numElements_ * ControlValueSize[type_] }; +} + +/** + * \copydoc ControlValue::operator==() + * \sa ControlValue::operator==() + * \sa ControlValueView::operator!=() + */ +bool ControlValueView::operator==(const ControlValueView &other) const +{ + if (type_ != other.type_) + return false; + + if (numElements_ != other.numElements_) + return false; + + if (isArray_ != other.isArray_) + return false; + + const auto d = data(); + + return memcmp(d.data(), other.data_, d.size_bytes()) == 0; +} + +/** + * \fn ControlValueView::operator!=() const + * \copydoc ControlValue::operator!=() + * \sa ControlValue::operator!=() + * \sa ControlValueView::operator==() + */ + +/** + * \fn template<typename T> T ControlValueView::get() const + * \copydoc ControlValue::get() + * \sa ControlValue::get() + */ + +/** + * \brief Insert a text representation of a value into an output stream + * \sa ControlValue::toString() + */ +std::ostream &operator<<(std::ostream &s, const ControlValueView &v) +{ + const auto type = v.type(); + if (type == ControlTypeNone) + return s << "None"; + + const auto *data = v.data().data(); + const auto numElements = v.numElements(); + + if (type == ControlTypeString) + return s << std::string_view(reinterpret_cast<const char *>(data), + numElements); + + const bool isArray = v.isArray(); + if (isArray) + s << "[ "; + + for (std::size_t i = 0; i < numElements; ++i) { + if (i > 0) + s << ", "; + + switch (type) { + case ControlTypeBool: { + const bool *value = reinterpret_cast<const bool *>(data); + s << (*value ? "true" : "false"); + break; + } + case ControlTypeByte: { + const auto *value = reinterpret_cast<const uint8_t *>(data); + s << static_cast<unsigned int>(*value); + break; + } + case ControlTypeUnsigned16: { + const auto *value = reinterpret_cast<const uint16_t *>(data); + s << *value; + break; + } + case ControlTypeUnsigned32: { + const auto *value = reinterpret_cast<const uint32_t *>(data); + s << *value; + break; + } + case ControlTypeInteger32: { + const auto *value = reinterpret_cast<const int32_t *>(data); + s << *value; + break; + } + case ControlTypeInteger64: { + const auto *value = reinterpret_cast<const int64_t *>(data); + s << *value; + break; + } + case ControlTypeFloat: { + const auto *value = reinterpret_cast<const float *>(data); + s << std::fixed << *value; + break; + } + case ControlTypeRectangle: { + const auto *value = reinterpret_cast<const Rectangle *>(data); + s << *value; + break; + } + case ControlTypeSize: { + const auto *value = reinterpret_cast<const Size *>(data); + s << *value; + break; + } + case ControlTypePoint: { + const auto *value = reinterpret_cast<const Point *>(data); + s << *value; + break; + } + case ControlTypeNone: + case ControlTypeString: + break; + } + + data += ControlValueSize[type]; + } + + if (isArray) + s << " ]"; + + return s; +} + /** * \class ControlId * \brief Control static metadata