[libcamera-devel,v5,01/17] libcamera: controls: Add a function to merge two control lists
diff mbox series

Message ID 20210503104152.34048-2-jacopo@jmondi.org
State Accepted
Delegated to: Laurent Pinchart
Headers show
Series
  • Support SensorTimestamp metadata
Related show

Commit Message

Jacopo Mondi May 3, 2021, 10:41 a.m. UTC
From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

Add a new ControlList::merge() function to merge two control lists by
copying in the values in the list passed as parameters.

This can be used by pipeline handlers to merge metadata they populate
with metadata received from an IPA.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
[reimplement the function by not using std::unordered_map::merge()]
Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 include/libcamera/controls.h |  2 ++
 src/libcamera/controls.cpp   | 30 ++++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)

Comments

Laurent Pinchart May 3, 2021, 12:25 p.m. UTC | #1
Hi Jacopo,

Thank you for the patch.

On Mon, May 03, 2021 at 12:41:36PM +0200, Jacopo Mondi wrote:
> From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
> Add a new ControlList::merge() function to merge two control lists by
> copying in the values in the list passed as parameters.
> 
> This can be used by pipeline handlers to merge metadata they populate
> with metadata received from an IPA.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> [reimplement the function by not using std::unordered_map::merge()]
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> ---
>  include/libcamera/controls.h |  2 ++
>  src/libcamera/controls.cpp   | 30 ++++++++++++++++++++++++++++++
>  2 files changed, 32 insertions(+)
> 
> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
> index 1a5690a5ccbe..1c9b37e617bc 100644
> --- a/include/libcamera/controls.h
> +++ b/include/libcamera/controls.h
> @@ -363,7 +363,9 @@ public:
>  
>  	bool empty() const { return controls_.empty(); }
>  	std::size_t size() const { return controls_.size(); }
> +
>  	void clear() { controls_.clear(); }
> +	void merge(const ControlList &source);
>  
>  	bool contains(const ControlId &id) const;
>  	bool contains(unsigned int id) const;
> diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
> index c58ed3946f3b..d8f104e3734b 100644
> --- a/src/libcamera/controls.cpp
> +++ b/src/libcamera/controls.cpp
> @@ -874,6 +874,36 @@ ControlList::ControlList(const ControlInfoMap &infoMap, ControlValidator *valida
>   * \brief Removes all controls from the list
>   */
>  
> +/**
> + * \brief Merge the \a source into the ControlList
> + * \param[in] source The ControlList to merge into this object
> + *
> + * Merging two control lists copies elements from the \a source and inserts
> + * them in *this. If the \a source contains elements whose key is already
> + * present in *this, then those elements are not overwritten.
> + *
> + * Only control lists created from the same ControlIdMap or ControlInfoMap may
> + * be merged. Attempting to do otherwise results in undefined behaviour.
> + *
> + * \todo Reimplement or implement an overloaded version which internally uses
> + * std::unordered_map::merge() and accepts a non-cost argument.

s/cost/const/

> + */
> +void ControlList::merge(const ControlList &source)
> +{
> +	ASSERT(idmap_ == source.idmap_);
> +
> +	for (const auto &ctrl : source) {
> +		if (contains(ctrl.first)) {
> +			const ControlId *id = idmap_->at(ctrl.first);
> +			LOG(Controls, Warning)
> +				<< "Control " << id->name() << " not overwritten";
> +			continue;
> +		}
> +
> +		set(ctrl.first, ctrl.second);
> +	}
> +}
> +
>  /**
>   * \brief Check if the list contains a control with the specified \a id
>   * \param[in] id The control ID
Kieran Bingham May 4, 2021, 8:11 a.m. UTC | #2
Hi Jacopo,

On 03/05/2021 11:41, Jacopo Mondi wrote:
> From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
> Add a new ControlList::merge() function to merge two control lists by
> copying in the values in the list passed as parameters.
> 
> This can be used by pipeline handlers to merge metadata they populate
> with metadata received from an IPA.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> [reimplement the function by not using std::unordered_map::merge()]
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>

With the cost/const fixed

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

> ---
>  include/libcamera/controls.h |  2 ++
>  src/libcamera/controls.cpp   | 30 ++++++++++++++++++++++++++++++
>  2 files changed, 32 insertions(+)
> 
> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
> index 1a5690a5ccbe..1c9b37e617bc 100644
> --- a/include/libcamera/controls.h
> +++ b/include/libcamera/controls.h
> @@ -363,7 +363,9 @@ public:
>  
>  	bool empty() const { return controls_.empty(); }
>  	std::size_t size() const { return controls_.size(); }
> +
>  	void clear() { controls_.clear(); }
> +	void merge(const ControlList &source);
>  
>  	bool contains(const ControlId &id) const;
>  	bool contains(unsigned int id) const;
> diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
> index c58ed3946f3b..d8f104e3734b 100644
> --- a/src/libcamera/controls.cpp
> +++ b/src/libcamera/controls.cpp
> @@ -874,6 +874,36 @@ ControlList::ControlList(const ControlInfoMap &infoMap, ControlValidator *valida
>   * \brief Removes all controls from the list
>   */
>  
> +/**
> + * \brief Merge the \a source into the ControlList
> + * \param[in] source The ControlList to merge into this object
> + *
> + * Merging two control lists copies elements from the \a source and inserts
> + * them in *this. If the \a source contains elements whose key is already
> + * present in *this, then those elements are not overwritten.
> + *
> + * Only control lists created from the same ControlIdMap or ControlInfoMap may
> + * be merged. Attempting to do otherwise results in undefined behaviour.
> + *
> + * \todo Reimplement or implement an overloaded version which internally uses
> + * std::unordered_map::merge() and accepts a non-cost argument.
> + */
> +void ControlList::merge(const ControlList &source)
> +{
> +	ASSERT(idmap_ == source.idmap_);
> +
> +	for (const auto &ctrl : source) {
> +		if (contains(ctrl.first)) {
> +			const ControlId *id = idmap_->at(ctrl.first);
> +			LOG(Controls, Warning)
> +				<< "Control " << id->name() << " not overwritten";
> +			continue;
> +		}
> +
> +		set(ctrl.first, ctrl.second);
> +	}
> +}
> +
>  /**
>   * \brief Check if the list contains a control with the specified \a id
>   * \param[in] id The control ID
>
Hirokazu Honda May 6, 2021, 4:58 a.m. UTC | #3
On Tue, May 4, 2021 at 5:11 PM Kieran Bingham <
kieran.bingham@ideasonboard.com> wrote:

> Hi Jacopo,
>
> On 03/05/2021 11:41, Jacopo Mondi wrote:
> > From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> >
> > Add a new ControlList::merge() function to merge two control lists by
> > copying in the values in the list passed as parameters.
> >
> > This can be used by pipeline handlers to merge metadata they populate
> > with metadata received from an IPA.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > [reimplement the function by not using std::unordered_map::merge()]
> > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
>
> With the cost/const fixed
>
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
>
> > ---
> >  include/libcamera/controls.h |  2 ++
> >  src/libcamera/controls.cpp   | 30 ++++++++++++++++++++++++++++++
> >  2 files changed, 32 insertions(+)
> >
> > diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
> > index 1a5690a5ccbe..1c9b37e617bc 100644
> > --- a/include/libcamera/controls.h
> > +++ b/include/libcamera/controls.h
> > @@ -363,7 +363,9 @@ public:
> >
> >       bool empty() const { return controls_.empty(); }
> >       std::size_t size() const { return controls_.size(); }
> > +
> >       void clear() { controls_.clear(); }
> > +     void merge(const ControlList &source);
> >
> >       bool contains(const ControlId &id) const;
> >       bool contains(unsigned int id) const;
> > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
> > index c58ed3946f3b..d8f104e3734b 100644
> > --- a/src/libcamera/controls.cpp
> > +++ b/src/libcamera/controls.cpp
> > @@ -874,6 +874,36 @@ ControlList::ControlList(const ControlInfoMap
> &infoMap, ControlValidator *valida
> >   * \brief Removes all controls from the list
> >   */
> >
> > +/**
> > + * \brief Merge the \a source into the ControlList
> > + * \param[in] source The ControlList to merge into this object
> > + *
> > + * Merging two control lists copies elements from the \a source and
> inserts
> > + * them in *this. If the \a source contains elements whose key is
> already
> > + * present in *this, then those elements are not overwritten.
> > + *
> > + * Only control lists created from the same ControlIdMap or
> ControlInfoMap may
> > + * be merged. Attempting to do otherwise results in undefined behaviour.
> > + *
> > + * \todo Reimplement or implement an overloaded version which
> internally uses
> > + * std::unordered_map::merge() and accepts a non-cost argument.
> > + */
> > +void ControlList::merge(const ControlList &source)
> > +{
> > +     ASSERT(idmap_ == source.idmap_);
>

Comparing std::unordered_map is O(NlogN) although the following for-loop is
O(M), where N is the size of idmap_ and M is the size of source.
Would you consider if this is worth doing?


> > +
> > +     for (const auto &ctrl : source) {
> > +             if (contains(ctrl.first)) {
> > +                     const ControlId *id = idmap_->at(ctrl.first);
> > +                     LOG(Controls, Warning)
>

I wonder if this is worth Warning. Debug seems to be natural for me.
Could you explain the reason?

Thanks,
-Hiro

> > +                             << "Control " << id->name() << " not
> overwritten";
> > +                     continue;
> > +             }
> > +
> > +             set(ctrl.first, ctrl.second);
> > +     }
> > +}
> > +
> >  /**
> >   * \brief Check if the list contains a control with the specified \a id
> >   * \param[in] id The control ID
> >
>
> --
> Regards
> --
> Kieran
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
>
Jacopo Mondi May 6, 2021, 7:45 a.m. UTC | #4
Hi Hiro,

On Thu, May 06, 2021 at 01:58:34PM +0900, Hirokazu Honda wrote:
> On Tue, May 4, 2021 at 5:11 PM Kieran Bingham <
> kieran.bingham@ideasonboard.com> wrote:
>
> > Hi Jacopo,
> >
> > On 03/05/2021 11:41, Jacopo Mondi wrote:
> > > From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > >
> > > Add a new ControlList::merge() function to merge two control lists by
> > > copying in the values in the list passed as parameters.
> > >
> > > This can be used by pipeline handlers to merge metadata they populate
> > > with metadata received from an IPA.
> > >
> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > [reimplement the function by not using std::unordered_map::merge()]
> > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> >
> > With the cost/const fixed
> >
> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> >
> > > ---
> > >  include/libcamera/controls.h |  2 ++
> > >  src/libcamera/controls.cpp   | 30 ++++++++++++++++++++++++++++++
> > >  2 files changed, 32 insertions(+)
> > >
> > > diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
> > > index 1a5690a5ccbe..1c9b37e617bc 100644
> > > --- a/include/libcamera/controls.h
> > > +++ b/include/libcamera/controls.h
> > > @@ -363,7 +363,9 @@ public:
> > >
> > >       bool empty() const { return controls_.empty(); }
> > >       std::size_t size() const { return controls_.size(); }
> > > +
> > >       void clear() { controls_.clear(); }
> > > +     void merge(const ControlList &source);
> > >
> > >       bool contains(const ControlId &id) const;
> > >       bool contains(unsigned int id) const;
> > > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
> > > index c58ed3946f3b..d8f104e3734b 100644
> > > --- a/src/libcamera/controls.cpp
> > > +++ b/src/libcamera/controls.cpp
> > > @@ -874,6 +874,36 @@ ControlList::ControlList(const ControlInfoMap
> > &infoMap, ControlValidator *valida
> > >   * \brief Removes all controls from the list
> > >   */
> > >
> > > +/**
> > > + * \brief Merge the \a source into the ControlList
> > > + * \param[in] source The ControlList to merge into this object
> > > + *
> > > + * Merging two control lists copies elements from the \a source and
> > inserts
> > > + * them in *this. If the \a source contains elements whose key is
> > already
> > > + * present in *this, then those elements are not overwritten.
> > > + *
> > > + * Only control lists created from the same ControlIdMap or
> > ControlInfoMap may
> > > + * be merged. Attempting to do otherwise results in undefined behaviour.
> > > + *
> > > + * \todo Reimplement or implement an overloaded version which
> > internally uses
> > > + * std::unordered_map::merge() and accepts a non-cost argument.
> > > + */
> > > +void ControlList::merge(const ControlList &source)
> > > +{
> > > +     ASSERT(idmap_ == source.idmap_);
> >
>
> Comparing std::unordered_map is O(NlogN) although the following for-loop is
> O(M), where N is the size of idmap_ and M is the size of source.
> Would you consider if this is worth doing?

Not sure I got this. Is your concern due to the idmap comparison ? Do
you wonder if it is necessary ?

It seems to me if we skip the comparison, things might seem to work
well, as the control values mapped to ids are stored in controls_ not
in idmap_.

Thing is, if you construct two control lists from two different
controls maps you have a risk of id collision, in two different
maps the same ControlId can be mapped to a different unsigned int, or
the other way around, two different ControlId can be mapped to the
same numeric identifier.

How likely is this to happen at the current development state in
mainline libcamera ? Close to 0 I would say.

However we should consider downstream implementation, which might use
custom controls and control maps, especially in IPA. They could be
tempted to mix them with lists constructed with the canonical
controls::controls and if they're not particularly careful bad things
can happen and can be quite difficult to debug.

Considering the usual length of the control lists we deal with, I
would say this check is worth it, but maybe I'm being paranoid and we
can safely skip it.

Or there might be better way to handle this, like

        for (const auto &ctrl : source) {
                if (idmap_[ctrl.first] != source.idmap_[ctrl.first])
                        /* Error out ludly */


        }

Although operator[] on an unordered map is linear in complexity in the
worst case, so we would get a O(NM) complexity, which is slightly
better than O(NlogNM). We're really talking details here, as with
lists of up to a few tens of items, I don't think the difference is
anyway relevant, but I appreciate the concern on staying as efficient
as we can...

>
>
> > > +
> > > +     for (const auto &ctrl : source) {
> > > +             if (contains(ctrl.first)) {
> > > +                     const ControlId *id = idmap_->at(ctrl.first);
> > > +                     LOG(Controls, Warning)
> >
>
> I wonder if this is worth Warning. Debug seems to be natural for me.
> Could you explain the reason?

Good question, I went for Warning because I think this is a condition
that should not happen, as an attempt to overwrite an existing control
through merging is suspicious and most probably not desired ? I
refrained from making this an error because it might be intentional,
but to me it's a condition that developers (mostly pipeline
developers) should be aware without going through the extensive Debug
log. As Warnings are less frequent, but ordinarily masked, I considered
this the right level. I can happily change this is if other debug
levels are considered better.

Thanks
   j

>
> Thanks,
> -Hiro
>
> > > +                             << "Control " << id->name() << " not
> > overwritten";
> > > +                     continue;
> > > +             }
> > > +
> > > +             set(ctrl.first, ctrl.second);
> > > +     }
> > > +}
> > > +
> > >  /**
> > >   * \brief Check if the list contains a control with the specified \a id
> > >   * \param[in] id The control ID
> > >
> >
> > --
> > Regards
> > --
> > Kieran
> > _______________________________________________
> > libcamera-devel mailing list
> > libcamera-devel@lists.libcamera.org
> > https://lists.libcamera.org/listinfo/libcamera-devel
> >
Hirokazu Honda May 6, 2021, 8:08 a.m. UTC | #5
Hi Jacopo,

On Thu, May 6, 2021 at 4:44 PM Jacopo Mondi <jacopo@jmondi.org> wrote:

> Hi Hiro,
>
> On Thu, May 06, 2021 at 01:58:34PM +0900, Hirokazu Honda wrote:
> > On Tue, May 4, 2021 at 5:11 PM Kieran Bingham <
> > kieran.bingham@ideasonboard.com> wrote:
> >
> > > Hi Jacopo,
> > >
> > > On 03/05/2021 11:41, Jacopo Mondi wrote:
> > > > From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > >
> > > > Add a new ControlList::merge() function to merge two control lists by
> > > > copying in the values in the list passed as parameters.
> > > >
> > > > This can be used by pipeline handlers to merge metadata they populate
> > > > with metadata received from an IPA.
> > > >
> > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > > > [reimplement the function by not using std::unordered_map::merge()]
> > > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> > >
> > > With the cost/const fixed
> > >
> > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > >
> > > > ---
> > > >  include/libcamera/controls.h |  2 ++
> > > >  src/libcamera/controls.cpp   | 30 ++++++++++++++++++++++++++++++
> > > >  2 files changed, 32 insertions(+)
> > > >
> > > > diff --git a/include/libcamera/controls.h
> b/include/libcamera/controls.h
> > > > index 1a5690a5ccbe..1c9b37e617bc 100644
> > > > --- a/include/libcamera/controls.h
> > > > +++ b/include/libcamera/controls.h
> > > > @@ -363,7 +363,9 @@ public:
> > > >
> > > >       bool empty() const { return controls_.empty(); }
> > > >       std::size_t size() const { return controls_.size(); }
> > > > +
> > > >       void clear() { controls_.clear(); }
> > > > +     void merge(const ControlList &source);
> > > >
> > > >       bool contains(const ControlId &id) const;
> > > >       bool contains(unsigned int id) const;
> > > > diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
> > > > index c58ed3946f3b..d8f104e3734b 100644
> > > > --- a/src/libcamera/controls.cpp
> > > > +++ b/src/libcamera/controls.cpp
> > > > @@ -874,6 +874,36 @@ ControlList::ControlList(const ControlInfoMap
> > > &infoMap, ControlValidator *valida
> > > >   * \brief Removes all controls from the list
> > > >   */
> > > >
> > > > +/**
> > > > + * \brief Merge the \a source into the ControlList
> > > > + * \param[in] source The ControlList to merge into this object
> > > > + *
> > > > + * Merging two control lists copies elements from the \a source and
> > > inserts
> > > > + * them in *this. If the \a source contains elements whose key is
> > > already
> > > > + * present in *this, then those elements are not overwritten.
> > > > + *
> > > > + * Only control lists created from the same ControlIdMap or
> > > ControlInfoMap may
> > > > + * be merged. Attempting to do otherwise results in undefined
> behaviour.
> > > > + *
> > > > + * \todo Reimplement or implement an overloaded version which
> > > internally uses
> > > > + * std::unordered_map::merge() and accepts a non-cost argument.
> > > > + */
> > > > +void ControlList::merge(const ControlList &source)
> > > > +{
> > > > +     ASSERT(idmap_ == source.idmap_);
> > >
> >
> > Comparing std::unordered_map is O(NlogN) although the following for-loop
> is
> > O(M), where N is the size of idmap_ and M is the size of source.
> > Would you consider if this is worth doing?
>
> Not sure I got this. Is your concern due to the idmap comparison ? Do
> you wonder if it is necessary ?
>
> It seems to me if we skip the comparison, things might seem to work
> well, as the control values mapped to ids are stored in controls_ not
> in idmap_.
>
> Thing is, if you construct two control lists from two different
> controls maps you have a risk of id collision, in two different
> maps the same ControlId can be mapped to a different unsigned int, or
> the other way around, two different ControlId can be mapped to the
> same numeric identifier.
>
> How likely is this to happen at the current development state in
> mainline libcamera ? Close to 0 I would say.
>
> However we should consider downstream implementation, which might use
> custom controls and control maps, especially in IPA. They could be
> tempted to mix them with lists constructed with the canonical
> controls::controls and if they're not particularly careful bad things
> can happen and can be quite difficult to debug.
>
> Considering the usual length of the control lists we deal with, I
> would say this check is worth it, but maybe I'm being paranoid and we
> can safely skip it.
>
> Or there might be better way to handle this, like
>
>         for (const auto &ctrl : source) {
>                 if (idmap_[ctrl.first] != source.idmap_[ctrl.first])
>                         /* Error out ludly */
>
>
>         }
>
> Although operator[] on an unordered map is linear in complexity in the
> worst case, so we would get a O(NM) complexity, which is slightly
> better than O(NlogNM). We're really talking details here, as with
> lists of up to a few tens of items, I don't think the difference is
> anyway relevant, but I appreciate the concern on staying as efficient
> as we can...
>
>
I missed the time complexity of std::unordered_map.

Comparing std::unordered_map is O(NlogN) although the following for-loop is
O(M), where N is the size of idmap_ and M is the size of source.

This should be O(N) and O(M), respectively.
So the original code is better than your proposal one.

>
> >
> > > > +
> > > > +     for (const auto &ctrl : source) {
> > > > +             if (contains(ctrl.first)) {
> > > > +                     const ControlId *id = idmap_->at(ctrl.first);
> > > > +                     LOG(Controls, Warning)
> > >
> >
> > I wonder if this is worth Warning. Debug seems to be natural for me.
> > Could you explain the reason?
>
> Good question, I went for Warning because I think this is a condition
> that should not happen, as an attempt to overwrite an existing control
> through merging is suspicious and most probably not desired ? I
> refrained from making this an error because it might be intentional,
> but to me it's a condition that developers (mostly pipeline
> developers) should be aware without going through the extensive Debug
> log. As Warnings are less frequent, but ordinarily masked, I considered
> this the right level. I can happily change this is if other debug
> levels are considered better.
>
>
Ack.

Reviewed-by: Hirokazu Honda <hiroh@chromium.org>


> Thanks
>    j
>
> >
> > Thanks,
> > -Hiro
> >
> > > > +                             << "Control " << id->name() << " not
> > > overwritten";
> > > > +                     continue;
> > > > +             }
> > > > +
> > > > +             set(ctrl.first, ctrl.second);
> > > > +     }
> > > > +}
> > > > +
> > > >  /**
> > > >   * \brief Check if the list contains a control with the specified
> \a id
> > > >   * \param[in] id The control ID
> > > >
> > >
> > > --
> > > Regards
> > > --
> > > Kieran
> > > _______________________________________________
> > > libcamera-devel mailing list
> > > libcamera-devel@lists.libcamera.org
> > > https://lists.libcamera.org/listinfo/libcamera-devel
> > >
>
Kieran Bingham May 6, 2021, 8:50 a.m. UTC | #6
On 06/05/2021 09:08, Hirokazu Honda wrote:
> Hi Jacopo,
> 
> On Thu, May 6, 2021 at 4:44 PM Jacopo Mondi <jacopo@jmondi.org
> <mailto:jacopo@jmondi.org>> wrote:
> 
>     Hi Hiro,
> 
>     On Thu, May 06, 2021 at 01:58:34PM +0900, Hirokazu Honda wrote:
>     > On Tue, May 4, 2021 at 5:11 PM Kieran Bingham <
>     > kieran.bingham@ideasonboard.com
>     <mailto:kieran.bingham@ideasonboard.com>> wrote:
>     >
>     > > Hi Jacopo,
>     > >
>     > > On 03/05/2021 11:41, Jacopo Mondi wrote:
>     > > > From: Laurent Pinchart <laurent.pinchart@ideasonboard.com
>     <mailto:laurent.pinchart@ideasonboard.com>>
>     > > >
>     > > > Add a new ControlList::merge() function to merge two control
>     lists by
>     > > > copying in the values in the list passed as parameters.
>     > > >
>     > > > This can be used by pipeline handlers to merge metadata they
>     populate
>     > > > with metadata received from an IPA.
>     > > >
>     > > > Signed-off-by: Laurent Pinchart
>     <laurent.pinchart@ideasonboard.com
>     <mailto:laurent.pinchart@ideasonboard.com>>
>     > > > [reimplement the function by not using
>     std::unordered_map::merge()]
>     > > > Signed-off-by: Jacopo Mondi <jacopo@jmondi.org
>     <mailto:jacopo@jmondi.org>>
>     > >
>     > > With the cost/const fixed
>     > >
>     > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com
>     <mailto:kieran.bingham@ideasonboard.com>>
>     > >
>     > > > ---
>     > > >  include/libcamera/controls.h |  2 ++
>     > > >  src/libcamera/controls.cpp   | 30 ++++++++++++++++++++++++++++++
>     > > >  2 files changed, 32 insertions(+)
>     > > >
>     > > > diff --git a/include/libcamera/controls.h
>     b/include/libcamera/controls.h
>     > > > index 1a5690a5ccbe..1c9b37e617bc 100644
>     > > > --- a/include/libcamera/controls.h
>     > > > +++ b/include/libcamera/controls.h
>     > > > @@ -363,7 +363,9 @@ public:
>     > > >
>     > > >       bool empty() const { return controls_.empty(); }
>     > > >       std::size_t size() const { return controls_.size(); }
>     > > > +
>     > > >       void clear() { controls_.clear(); }
>     > > > +     void merge(const ControlList &source);
>     > > >
>     > > >       bool contains(const ControlId &id) const;
>     > > >       bool contains(unsigned int id) const;
>     > > > diff --git a/src/libcamera/controls.cpp
>     b/src/libcamera/controls.cpp
>     > > > index c58ed3946f3b..d8f104e3734b 100644
>     > > > --- a/src/libcamera/controls.cpp
>     > > > +++ b/src/libcamera/controls.cpp
>     > > > @@ -874,6 +874,36 @@ ControlList::ControlList(const ControlInfoMap
>     > > &infoMap, ControlValidator *valida
>     > > >   * \brief Removes all controls from the list
>     > > >   */
>     > > >
>     > > > +/**
>     > > > + * \brief Merge the \a source into the ControlList
>     > > > + * \param[in] source The ControlList to merge into this object
>     > > > + *
>     > > > + * Merging two control lists copies elements from the \a
>     source and
>     > > inserts
>     > > > + * them in *this. If the \a source contains elements whose key is
>     > > already
>     > > > + * present in *this, then those elements are not overwritten.
>     > > > + *
>     > > > + * Only control lists created from the same ControlIdMap or
>     > > ControlInfoMap may
>     > > > + * be merged. Attempting to do otherwise results in undefined
>     behaviour.
>     > > > + *
>     > > > + * \todo Reimplement or implement an overloaded version which
>     > > internally uses
>     > > > + * std::unordered_map::merge() and accepts a non-cost argument.
>     > > > + */
>     > > > +void ControlList::merge(const ControlList &source)
>     > > > +{
>     > > > +     ASSERT(idmap_ == source.idmap_);
>     > >
>     >
>     > Comparing std::unordered_map is O(NlogN) although the following
>     for-loop is
>     > O(M), where N is the size of idmap_ and M is the size of source.
>     > Would you consider if this is worth doing?
> 
>     Not sure I got this. Is your concern due to the idmap comparison ? Do
>     you wonder if it is necessary ?
> 
>     It seems to me if we skip the comparison, things might seem to work
>     well, as the control values mapped to ids are stored in controls_ not
>     in idmap_.
> 
>     Thing is, if you construct two control lists from two different
>     controls maps you have a risk of id collision, in two different
>     maps the same ControlId can be mapped to a different unsigned int, or
>     the other way around, two different ControlId can be mapped to the
>     same numeric identifier.
> 
>     How likely is this to happen at the current development state in
>     mainline libcamera ? Close to 0 I would say.
> 
>     However we should consider downstream implementation, which might use
>     custom controls and control maps, especially in IPA. They could be
>     tempted to mix them with lists constructed with the canonical
>     controls::controls and if they're not particularly careful bad things
>     can happen and can be quite difficult to debug.
> 
>     Considering the usual length of the control lists we deal with, I
>     would say this check is worth it, but maybe I'm being paranoid and we
>     can safely skip it.
> 
>     Or there might be better way to handle this, like
> 
>             for (const auto &ctrl : source) {
>                     if (idmap_[ctrl.first] != source.idmap_[ctrl.first])
>                             /* Error out ludly */
> 
> 
>             }
> 
>     Although operator[] on an unordered map is linear in complexity in the
>     worst case, so we would get a O(NM) complexity, which is slightly
>     better than O(NlogNM). We're really talking details here, as with
>     lists of up to a few tens of items, I don't think the difference is
>     anyway relevant, but I appreciate the concern on staying as efficient
>     as we can...
> 
> 
> I missed the time complexity of std::unordered_map.
> 
>     Comparing std::unordered_map is O(NlogN) although the following
>     for-loop is
>     O(M), where N is the size of idmap_ and M is the size of source.
> 
> This should be O(N) and O(M), respectively.
> So the original code is better than your proposal one.

I like the thorough investigations to find out the best way to compare a
map - but I'm weary it's a bit moot.

Isn't this an incredibly cheap pointer comparison?

	ASSERT(idmap_ == source.idmap_);


Or is this not what we're querying here (it's hard to tell).

The idmap_ of a ControlList is of type:

class ControlList
{
private:
	const ControlIdMap *idmap_;
}

So the assert is guaranteeing that both control lists are using /exactly
the same/ id map, not that the contents of each idmap is the same.



> 
>     >
>     >
>     > > > +
>     > > > +     for (const auto &ctrl : source) {
>     > > > +             if (contains(ctrl.first)) {
>     > > > +                     const ControlId *id =
>     idmap_->at(ctrl.first);
>     > > > +                     LOG(Controls, Warning)
>     > >
>     >
>     > I wonder if this is worth Warning. Debug seems to be natural for me.
>     > Could you explain the reason?
> 
>     Good question, I went for Warning because I think this is a condition
>     that should not happen, as an attempt to overwrite an existing control
>     through merging is suspicious and most probably not desired ? I
>     refrained from making this an error because it might be intentional,
>     but to me it's a condition that developers (mostly pipeline
>     developers) should be aware without going through the extensive Debug
>     log. As Warnings are less frequent, but ordinarily masked, I considered
>     this the right level. I can happily change this is if other debug
>     levels are considered better.
> 

I agree here, I think it's better to be louder here, because otherwise
the user could get behaviour they weren't expecting.


> 
> Ack.
> 
> Reviewed-by: Hirokazu Honda <hiroh@chromium.org <mailto:hiroh@chromium.org>>
>  
> 
>     Thanks
>        j
> 
>     >
>     > Thanks,
>     > -Hiro
>     >
>     > > > +                             << "Control " << id->name() << " not
>     > > overwritten";
>     > > > +                     continue;
>     > > > +             }
>     > > > +
>     > > > +             set(ctrl.first, ctrl.second);
>     > > > +     }
>     > > > +}
>     > > > +
>     > > >  /**
>     > > >   * \brief Check if the list contains a control with the
>     specified \a id
>     > > >   * \param[in] id The control ID
>     > > >
>     > >
>     > > --
>     > > Regards
>     > > --
>     > > Kieran
>     > > _______________________________________________
>     > > libcamera-devel mailing list
>     > > libcamera-devel@lists.libcamera.org
>     <mailto:libcamera-devel@lists.libcamera.org>
>     > > https://lists.libcamera.org/listinfo/libcamera-devel
>     <https://lists.libcamera.org/listinfo/libcamera-devel>
>     > >
>

Patch
diff mbox series

diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
index 1a5690a5ccbe..1c9b37e617bc 100644
--- a/include/libcamera/controls.h
+++ b/include/libcamera/controls.h
@@ -363,7 +363,9 @@  public:
 
 	bool empty() const { return controls_.empty(); }
 	std::size_t size() const { return controls_.size(); }
+
 	void clear() { controls_.clear(); }
+	void merge(const ControlList &source);
 
 	bool contains(const ControlId &id) const;
 	bool contains(unsigned int id) const;
diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
index c58ed3946f3b..d8f104e3734b 100644
--- a/src/libcamera/controls.cpp
+++ b/src/libcamera/controls.cpp
@@ -874,6 +874,36 @@  ControlList::ControlList(const ControlInfoMap &infoMap, ControlValidator *valida
  * \brief Removes all controls from the list
  */
 
+/**
+ * \brief Merge the \a source into the ControlList
+ * \param[in] source The ControlList to merge into this object
+ *
+ * Merging two control lists copies elements from the \a source and inserts
+ * them in *this. If the \a source contains elements whose key is already
+ * present in *this, then those elements are not overwritten.
+ *
+ * Only control lists created from the same ControlIdMap or ControlInfoMap may
+ * be merged. Attempting to do otherwise results in undefined behaviour.
+ *
+ * \todo Reimplement or implement an overloaded version which internally uses
+ * std::unordered_map::merge() and accepts a non-cost argument.
+ */
+void ControlList::merge(const ControlList &source)
+{
+	ASSERT(idmap_ == source.idmap_);
+
+	for (const auto &ctrl : source) {
+		if (contains(ctrl.first)) {
+			const ControlId *id = idmap_->at(ctrl.first);
+			LOG(Controls, Warning)
+				<< "Control " << id->name() << " not overwritten";
+			continue;
+		}
+
+		set(ctrl.first, ctrl.second);
+	}
+}
+
 /**
  * \brief Check if the list contains a control with the specified \a id
  * \param[in] id The control ID