[RFC,v3,02/22] libcamera: controls: Add `ControlValueView`
diff mbox series

Message ID 20251030165816.1095180-3-barnabas.pocze@ideasonboard.com
State New
Headers show
Series
  • libcamera: Add `MetadataList`
Related show

Commit Message

Barnabás Pőcze Oct. 30, 2025, 4:57 p.m. UTC
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(-)

Comments

Kieran Bingham Oct. 31, 2025, 8:44 a.m. UTC | #1
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
>
Dan Scally Nov. 2, 2025, 8:16 a.m. UTC | #2
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

Patch
diff mbox series

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