[{"id":37599,"web_url":"https://patchwork.libcamera.org/comment/37599/","msgid":"<3544249d-c772-4352-a2aa-28bbbe405dcd@ideasonboard.com>","date":"2026-01-13T12:45:22","subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"2026. 01. 13. 1:08 keltezéssel, Laurent Pinchart írta:\n> The GlobalConfiguration class will need to add nested children to a\n> ValueNode. Add a new overload to the add() function for this purpose.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>   include/libcamera/internal/value_node.h |  3 ++\n>   src/libcamera/value_node.cpp            | 56 +++++++++++++++++++++++++\n>   2 files changed, 59 insertions(+)\n> \n> diff --git a/include/libcamera/internal/value_node.h b/include/libcamera/internal/value_node.h\n> index c285be4957ed..abb0991fd87e 100644\n> --- a/include/libcamera/internal/value_node.h\n> +++ b/include/libcamera/internal/value_node.h\n> @@ -8,6 +8,7 @@\n>   \n>   #pragma once\n>   \n> +#include <initializer_list>\n>   #include <iterator>\n>   #include <map>\n>   #include <memory>\n> @@ -237,6 +238,8 @@ public:\n>   \n>   \tValueNode *add(std::unique_ptr<ValueNode> child);\n>   \tValueNode *add(std::string key, std::unique_ptr<ValueNode> child);\n> +\tValueNode *add(std::initializer_list<std::string_view> path,\n> +\t\t       std::unique_ptr<ValueNode> child);\n>   \n>   private:\n>   \tLIBCAMERA_DISABLE_COPY_AND_MOVE(ValueNode)\n> diff --git a/src/libcamera/value_node.cpp b/src/libcamera/value_node.cpp\n> index 50b23284f930..1256adf8361d 100644\n> --- a/src/libcamera/value_node.cpp\n> +++ b/src/libcamera/value_node.cpp\n> @@ -13,6 +13,8 @@\n>   #include <string>\n>   #include <vector>\n>   \n> +#include <libcamera/base/log.h>\n> +#include <libcamera/base/span.h>\n>   #include <libcamera/base/utils.h>\n>   \n>   /**\n> @@ -22,6 +24,8 @@\n>   \n>   namespace libcamera {\n>   \n> +LOG_DEFINE_CATEGORY(ValueNode)\n> +\n>   namespace {\n>   \n>   /* Empty static ValueNode as a safe result for invalid operations */\n> @@ -539,4 +543,56 @@ ValueNode *ValueNode::add(std::string key, std::unique_ptr<ValueNode> child)\n>   \treturn elem.value.get();\n>   }\n>   \n> +/**\n> + * \\brief Add a child node at the given path\n> + * \\param[in] path The path\n> + * \\param[in] child The child node\n> + *\n> + * Add the \\a child node at the given \\a path starting at this node. Missing\n> + * nodes are created along the path. Nodes along the path must be empty, in\n> + * which case they are converted to the Type::Dictionary type, be a dictionary,\n\nIt's a small thing, but I'd put the \"in ... type\" part in parenthesis because for\nme it currently disrupts the flow of the list a bit.\n\n\n> + * or be missing. Otherwise, the \\a child is discarded and the function returns\n> + * a nullptr.\n\nI think the caveat that already created nodes along `path` are left in place\nshould be mentioned. Maybe even as a `\\note`.\n\nOr maybe the first non-existent child can be recorded and everything up to that\ncan be removed in a `utils::scope_exit` in case of \"failure\". But maybe not worth\nthe complexity.\n\n\n> + *\n> + * Path elements are unique in the context of a parent node. If a child with the\n> + * same \\a key already exist at the end of the path, the \\a child is discarded\n> + * and the function returns a nullptr.\n> + *\n> + * \\return A pointer to the \\a child node if successfully added, nullptr\n> + * otherwise\n> + */\n> +ValueNode *ValueNode::add(std::initializer_list<std::string_view> path,\n> +\t\t\t  std::unique_ptr<ValueNode> child)\n\nI think maybe we could use `std::unique_ptr<ValueNode> &&child` and only actually\nmove the value if the \"insertion\" will succeed. (Needs the same change in `ValueNode::add()`.)\n\n\n> +{\n> +\tif (!path.size())\n> +\t\treturn NULL;\n\n   nullptr\n\n\n> +\n> +\tValueNode *node = this;\n> +\n> +\tfor (const auto [i, name] : utils::enumerate(path)) {\n> +\t\tauto iter = node->dictionary_.find(name);\n> +\t\tif (iter == node->dictionary_.end()) {\n> +\t\t\tstd::unique_ptr<ValueNode> obj;\n> +\n> +\t\t\tif (i < path.size() - 1)\n> +\t\t\t\tobj = std::make_unique<ValueNode>();\n> +\t\t\telse\n> +\t\t\t\tobj = std::move(child);\n> +\n> +\t\t\tnode = node->add(std::string{ name }, std::move(obj));\n> +\t\t\tif (!node) {\n> +\t\t\t\tSpan<const std::string_view> pathName{ std::data(path), i + 1 };\n> +\t\t\t\tLOG(ValueNode, Error)\n> +\t\t\t\t\t<< \"Failed to populate '\"\n> +\t\t\t\t\t<< utils::join(pathName, \"/\") << \"'\";\n> +\t\t\t\treturn nullptr;\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tnode = iter->second;\n> +\t\t}\n> +\t}\n> +\n> +\treturn node;\n> +}\n> +\n>   } /* namespace libcamera */","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id C605ABDCBF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 13 Jan 2026 12:45:27 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 097FF61FA3;\n\tTue, 13 Jan 2026 13:45:27 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8524061FA0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 13 Jan 2026 13:45:25 +0100 (CET)","from [192.168.33.30] (185.221.143.114.nat.pool.zt.hu\n\t[185.221.143.114])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 67C7950A;\n\tTue, 13 Jan 2026 13:44:59 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"edH+IhyW\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1768308299;\n\tbh=qYJKnfcgr4Xp9tW9JbHV71bWpuPkdMdOeNytb/tV8GE=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=edH+IhyWrID+8+E04mKFmomc78QSxiTeBEfV1nsrsP0+erfEB247IoxsCixHTdXUB\n\t1h+KZIAt9Y59gi/00M3PWP43qJVzjg7Io7cvnPD+0Qe58ChmJEIifa6hO6I1lutu55\n\tOoJcPnS141++4nUCr3EcczkgC+FHf5jLfWVd71dE=","Message-ID":"<3544249d-c772-4352-a2aa-28bbbe405dcd@ideasonboard.com>","Date":"Tue, 13 Jan 2026 13:45:22 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20260113000808.15395-1-laurent.pinchart@ideasonboard.com>\n\t<20260113000808.15395-30-laurent.pinchart@ideasonboard.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<20260113000808.15395-30-laurent.pinchart@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":37603,"web_url":"https://patchwork.libcamera.org/comment/37603/","msgid":"<20260113135030.GG6198@pendragon.ideasonboard.com>","date":"2026-01-13T13:50:30","subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"On Tue, Jan 13, 2026 at 01:45:22PM +0100, Barnabás Pőcze wrote:\n> 2026. 01. 13. 1:08 keltezéssel, Laurent Pinchart írta:\n> > The GlobalConfiguration class will need to add nested children to a\n> > ValueNode. Add a new overload to the add() function for this purpose.\n> > \n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >   include/libcamera/internal/value_node.h |  3 ++\n> >   src/libcamera/value_node.cpp            | 56 +++++++++++++++++++++++++\n> >   2 files changed, 59 insertions(+)\n> > \n> > diff --git a/include/libcamera/internal/value_node.h b/include/libcamera/internal/value_node.h\n> > index c285be4957ed..abb0991fd87e 100644\n> > --- a/include/libcamera/internal/value_node.h\n> > +++ b/include/libcamera/internal/value_node.h\n> > @@ -8,6 +8,7 @@\n> >   \n> >   #pragma once\n> >   \n> > +#include <initializer_list>\n> >   #include <iterator>\n> >   #include <map>\n> >   #include <memory>\n> > @@ -237,6 +238,8 @@ public:\n> >   \n> >   \tValueNode *add(std::unique_ptr<ValueNode> child);\n> >   \tValueNode *add(std::string key, std::unique_ptr<ValueNode> child);\n> > +\tValueNode *add(std::initializer_list<std::string_view> path,\n> > +\t\t       std::unique_ptr<ValueNode> child);\n> >   \n> >   private:\n> >   \tLIBCAMERA_DISABLE_COPY_AND_MOVE(ValueNode)\n> > diff --git a/src/libcamera/value_node.cpp b/src/libcamera/value_node.cpp\n> > index 50b23284f930..1256adf8361d 100644\n> > --- a/src/libcamera/value_node.cpp\n> > +++ b/src/libcamera/value_node.cpp\n> > @@ -13,6 +13,8 @@\n> >   #include <string>\n> >   #include <vector>\n> >   \n> > +#include <libcamera/base/log.h>\n> > +#include <libcamera/base/span.h>\n> >   #include <libcamera/base/utils.h>\n> >   \n> >   /**\n> > @@ -22,6 +24,8 @@\n> >   \n> >   namespace libcamera {\n> >   \n> > +LOG_DEFINE_CATEGORY(ValueNode)\n> > +\n> >   namespace {\n> >   \n> >   /* Empty static ValueNode as a safe result for invalid operations */\n> > @@ -539,4 +543,56 @@ ValueNode *ValueNode::add(std::string key, std::unique_ptr<ValueNode> child)\n> >   \treturn elem.value.get();\n> >   }\n> >   \n> > +/**\n> > + * \\brief Add a child node at the given path\n> > + * \\param[in] path The path\n> > + * \\param[in] child The child node\n> > + *\n> > + * Add the \\a child node at the given \\a path starting at this node. Missing\n> > + * nodes are created along the path. Nodes along the path must be empty, in\n> > + * which case they are converted to the Type::Dictionary type, be a dictionary,\n> \n> It's a small thing, but I'd put the \"in ... type\" part in parenthesis because for\n> me it currently disrupts the flow of the list a bit.\n\nGood idea.\n\n> > + * or be missing. Otherwise, the \\a child is discarded and the function returns\n> > + * a nullptr.\n> \n> I think the caveat that already created nodes along `path` are left in place\n> should be mentioned. Maybe even as a `\\note`.\n> \n> Or maybe the first non-existent child can be recorded and everything up to that\n> can be removed in a `utils::scope_exit` in case of \"failure\". But maybe not worth\n> the complexity.\n\nI'll add a note.\n\n> > + *\n> > + * Path elements are unique in the context of a parent node. If a child with the\n> > + * same \\a key already exist at the end of the path, the \\a child is discarded\n> > + * and the function returns a nullptr.\n> > + *\n> > + * \\return A pointer to the \\a child node if successfully added, nullptr\n> > + * otherwise\n> > + */\n> > +ValueNode *ValueNode::add(std::initializer_list<std::string_view> path,\n> > +\t\t\t  std::unique_ptr<ValueNode> child)\n> \n> I think maybe we could use `std::unique_ptr<ValueNode> &&child` and only actually\n> move the value if the \"insertion\" will succeed. (Needs the same change in `ValueNode::add()`.)\n\nHmmm... I think that would be confusing. The caller would need to do\n\n\tValueNode &node = ...\n\tstd::unique_ptr<ValueNode> child = ...;\n\n\tnode.add(\"path\", std::move(child));\n\nbut the move wouldn't be performed in case of failure. Sure, the child\nwould eventually be freed, but at an unexpected point, and the\nstd::move() call would be misleading.\n\n> > +{\n> > +\tif (!path.size())\n> > +\t\treturn NULL;\n> \n>    nullptr\n\nOops.\n\n> > +\n> > +\tValueNode *node = this;\n> > +\n> > +\tfor (const auto [i, name] : utils::enumerate(path)) {\n> > +\t\tauto iter = node->dictionary_.find(name);\n> > +\t\tif (iter == node->dictionary_.end()) {\n> > +\t\t\tstd::unique_ptr<ValueNode> obj;\n> > +\n> > +\t\t\tif (i < path.size() - 1)\n> > +\t\t\t\tobj = std::make_unique<ValueNode>();\n> > +\t\t\telse\n> > +\t\t\t\tobj = std::move(child);\n> > +\n> > +\t\t\tnode = node->add(std::string{ name }, std::move(obj));\n> > +\t\t\tif (!node) {\n> > +\t\t\t\tSpan<const std::string_view> pathName{ std::data(path), i + 1 };\n> > +\t\t\t\tLOG(ValueNode, Error)\n> > +\t\t\t\t\t<< \"Failed to populate '\"\n> > +\t\t\t\t\t<< utils::join(pathName, \"/\") << \"'\";\n> > +\t\t\t\treturn nullptr;\n> > +\t\t\t}\n> > +\t\t} else {\n> > +\t\t\tnode = iter->second;\n> > +\t\t}\n> > +\t}\n> > +\n> > +\treturn node;\n> > +}\n> > +\n> >   } /* namespace libcamera */","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id D2C94BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 13 Jan 2026 13:50:53 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1693261FA3;\n\tTue, 13 Jan 2026 14:50:53 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 184B661FA0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 13 Jan 2026 14:50:51 +0100 (CET)","from pendragon.ideasonboard.com (81-175-209-152.bb.dnainternet.fi\n\t[81.175.209.152])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id C7571316;\n\tTue, 13 Jan 2026 14:50:24 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"I+LcrfW0\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1768312225;\n\tbh=wafPWNw3/bhYZGPFOz5g6p7UvYtLRc2qRnpYuapdldA=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=I+LcrfW0rRHgMIVDZZxNf2ulvubcJf6CJdVej1utr8hV7eTXNSH2bc9PBvDVjzh5w\n\tA92XVGG1TYe8683opOYWIt3cyirujf43zM9+IAHb/TDlEX2NY4KlbyEUB9AuecjIae\n\tB5H84d1YWFb4eUnk6SPEvl3WSLmPZn+NH2eORUmw=","Date":"Tue, 13 Jan 2026 15:50:30 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","Message-ID":"<20260113135030.GG6198@pendragon.ideasonboard.com>","References":"<20260113000808.15395-1-laurent.pinchart@ideasonboard.com>\n\t<20260113000808.15395-30-laurent.pinchart@ideasonboard.com>\n\t<3544249d-c772-4352-a2aa-28bbbe405dcd@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<3544249d-c772-4352-a2aa-28bbbe405dcd@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":37613,"web_url":"https://patchwork.libcamera.org/comment/37613/","msgid":"<50f60673-9c04-4cc9-b9a2-d2a974f16c80@ideasonboard.com>","date":"2026-01-13T15:09:18","subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"2026. 01. 13. 14:50 keltezéssel, Laurent Pinchart írta:\n> On Tue, Jan 13, 2026 at 01:45:22PM +0100, Barnabás Pőcze wrote:\n>> 2026. 01. 13. 1:08 keltezéssel, Laurent Pinchart írta:\n>>> The GlobalConfiguration class will need to add nested children to a\n>>> ValueNode. Add a new overload to the add() function for this purpose.\n>>>\n>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n>>> ---\n>>>    include/libcamera/internal/value_node.h |  3 ++\n>>>    src/libcamera/value_node.cpp            | 56 +++++++++++++++++++++++++\n>>>    2 files changed, 59 insertions(+)\n>>>\n>>> diff --git a/include/libcamera/internal/value_node.h b/include/libcamera/internal/value_node.h\n>>> index c285be4957ed..abb0991fd87e 100644\n>>> --- a/include/libcamera/internal/value_node.h\n>>> +++ b/include/libcamera/internal/value_node.h\n>>> @@ -8,6 +8,7 @@\n>>>    \n>>>    #pragma once\n>>>    \n>>> +#include <initializer_list>\n>>>    #include <iterator>\n>>>    #include <map>\n>>>    #include <memory>\n>>> @@ -237,6 +238,8 @@ public:\n>>>    \n>>>    \tValueNode *add(std::unique_ptr<ValueNode> child);\n>>>    \tValueNode *add(std::string key, std::unique_ptr<ValueNode> child);\n>>> +\tValueNode *add(std::initializer_list<std::string_view> path,\n>>> +\t\t       std::unique_ptr<ValueNode> child);\n>>>    \n>>>    private:\n>>>    \tLIBCAMERA_DISABLE_COPY_AND_MOVE(ValueNode)\n>>> diff --git a/src/libcamera/value_node.cpp b/src/libcamera/value_node.cpp\n>>> index 50b23284f930..1256adf8361d 100644\n>>> --- a/src/libcamera/value_node.cpp\n>>> +++ b/src/libcamera/value_node.cpp\n>>> @@ -13,6 +13,8 @@\n>>>    #include <string>\n>>>    #include <vector>\n>>>    \n>>> +#include <libcamera/base/log.h>\n>>> +#include <libcamera/base/span.h>\n>>>    #include <libcamera/base/utils.h>\n>>>    \n>>>    /**\n>>> @@ -22,6 +24,8 @@\n>>>    \n>>>    namespace libcamera {\n>>>    \n>>> +LOG_DEFINE_CATEGORY(ValueNode)\n>>> +\n>>>    namespace {\n>>>    \n>>>    /* Empty static ValueNode as a safe result for invalid operations */\n>>> @@ -539,4 +543,56 @@ ValueNode *ValueNode::add(std::string key, std::unique_ptr<ValueNode> child)\n>>>    \treturn elem.value.get();\n>>>    }\n>>>    \n>>> +/**\n>>> + * \\brief Add a child node at the given path\n>>> + * \\param[in] path The path\n>>> + * \\param[in] child The child node\n>>> + *\n>>> + * Add the \\a child node at the given \\a path starting at this node. Missing\n>>> + * nodes are created along the path. Nodes along the path must be empty, in\n>>> + * which case they are converted to the Type::Dictionary type, be a dictionary,\n>>\n>> It's a small thing, but I'd put the \"in ... type\" part in parenthesis because for\n>> me it currently disrupts the flow of the list a bit.\n> \n> Good idea.\n> \n>>> + * or be missing. Otherwise, the \\a child is discarded and the function returns\n>>> + * a nullptr.\n>>\n>> I think the caveat that already created nodes along `path` are left in place\n>> should be mentioned. Maybe even as a `\\note`.\n>>\n>> Or maybe the first non-existent child can be recorded and everything up to that\n>> can be removed in a `utils::scope_exit` in case of \"failure\". But maybe not worth\n>> the complexity.\n> \n> I'll add a note.\n> \n>>> + *\n>>> + * Path elements are unique in the context of a parent node. If a child with the\n>>> + * same \\a key already exist at the end of the path, the \\a child is discarded\n>>> + * and the function returns a nullptr.\n>>> + *\n>>> + * \\return A pointer to the \\a child node if successfully added, nullptr\n>>> + * otherwise\n>>> + */\n>>> +ValueNode *ValueNode::add(std::initializer_list<std::string_view> path,\n>>> +\t\t\t  std::unique_ptr<ValueNode> child)\n>>\n>> I think maybe we could use `std::unique_ptr<ValueNode> &&child` and only actually\n>> move the value if the \"insertion\" will succeed. (Needs the same change in `ValueNode::add()`.)\n> \n> Hmmm... I think that would be confusing. The caller would need to do\n> \n> \tValueNode &node = ...\n> \tstd::unique_ptr<ValueNode> child = ...;\n> \n> \tnode.add(\"path\", std::move(child));\n\nWell, they can do `node.add(\"...\", std::make_unique<ValueNode>(...))` as well.\nBut then the difference is not really visible.\n\n\n> \n> but the move wouldn't be performed in case of failure. Sure, the child\n> would eventually be freed, but at an unexpected point, and the\n> std::move() call would be misleading.\n\nI suppose it depends on one's expectations about `std::move()`. Technically\nit is a cast, so it doesn't do anything in and of itself. So for example I\nthink you have the same behaviour if e.g. `vec.push_back(std::move(...))` fails.\n\n\n> \n>>> +{\n>>> +\tif (!path.size())\n>>> +\t\treturn NULL;\n>>\n>>     nullptr\n> \n> Oops.\n> \n>>> +\n>>> +\tValueNode *node = this;\n>>> +\n>>> +\tfor (const auto [i, name] : utils::enumerate(path)) {\n>>> +\t\tauto iter = node->dictionary_.find(name);\n>>> +\t\tif (iter == node->dictionary_.end()) {\n>>> +\t\t\tstd::unique_ptr<ValueNode> obj;\n>>> +\n>>> +\t\t\tif (i < path.size() - 1)\n>>> +\t\t\t\tobj = std::make_unique<ValueNode>();\n>>> +\t\t\telse\n>>> +\t\t\t\tobj = std::move(child);\n>>> +\n>>> +\t\t\tnode = node->add(std::string{ name }, std::move(obj));\n>>> +\t\t\tif (!node) {\n>>> +\t\t\t\tSpan<const std::string_view> pathName{ std::data(path), i + 1 };\n>>> +\t\t\t\tLOG(ValueNode, Error)\n>>> +\t\t\t\t\t<< \"Failed to populate '\"\n>>> +\t\t\t\t\t<< utils::join(pathName, \"/\") << \"'\";\n>>> +\t\t\t\treturn nullptr;\n>>> +\t\t\t}\n>>> +\t\t} else {\n>>> +\t\t\tnode = iter->second;\n>>> +\t\t}\n>>> +\t}\n>>> +\n>>> +\treturn node;\n>>> +}\n>>> +\n>>>    } /* namespace libcamera */\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id D30BBBE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 13 Jan 2026 15:09:23 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0540D61FA0;\n\tTue, 13 Jan 2026 16:09:23 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B101861FA0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 13 Jan 2026 16:09:21 +0100 (CET)","from [192.168.33.30] (185.221.143.114.nat.pool.zt.hu\n\t[185.221.143.114])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 959AE316;\n\tTue, 13 Jan 2026 16:08:55 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"o96H+WWX\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1768316935;\n\tbh=dBw6J7EQepnKdQiparxKR66tSFl1Y9YIVQt2ae6KiIg=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=o96H+WWXXUnK89JgHIBSrmueJkKcVjEZ8hzB4cqHzkcoLViR65B9Lvi4/Zg0gv59N\n\tKYzVkywNomyz5O1bGyl2k0QfGYP1xjGw0zTmdRCbQ2ZOB9cziD2kQGc7iQyR/CKb3o\n\t4oQtC57neu14EXkoUtm6yaKBX8VcogtBqIgj8310=","Message-ID":"<50f60673-9c04-4cc9-b9a2-d2a974f16c80@ideasonboard.com>","Date":"Tue, 13 Jan 2026 16:09:18 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","References":"<20260113000808.15395-1-laurent.pinchart@ideasonboard.com>\n\t<20260113000808.15395-30-laurent.pinchart@ideasonboard.com>\n\t<3544249d-c772-4352-a2aa-28bbbe405dcd@ideasonboard.com>\n\t<20260113135030.GG6198@pendragon.ideasonboard.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<20260113135030.GG6198@pendragon.ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":37717,"web_url":"https://patchwork.libcamera.org/comment/37717/","msgid":"<20260119020024.GE7759@pendragon.ideasonboard.com>","date":"2026-01-19T02:00:24","subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"On Tue, Jan 13, 2026 at 04:09:18PM +0100, Barnabás Pőcze wrote:\n> 2026. 01. 13. 14:50 keltezéssel, Laurent Pinchart írta:\n> > On Tue, Jan 13, 2026 at 01:45:22PM +0100, Barnabás Pőcze wrote:\n> >> 2026. 01. 13. 1:08 keltezéssel, Laurent Pinchart írta:\n> >>> The GlobalConfiguration class will need to add nested children to a\n> >>> ValueNode. Add a new overload to the add() function for this purpose.\n> >>>\n> >>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> >>> ---\n> >>>    include/libcamera/internal/value_node.h |  3 ++\n> >>>    src/libcamera/value_node.cpp            | 56 +++++++++++++++++++++++++\n> >>>    2 files changed, 59 insertions(+)\n> >>>\n> >>> diff --git a/include/libcamera/internal/value_node.h b/include/libcamera/internal/value_node.h\n> >>> index c285be4957ed..abb0991fd87e 100644\n> >>> --- a/include/libcamera/internal/value_node.h\n> >>> +++ b/include/libcamera/internal/value_node.h\n> >>> @@ -8,6 +8,7 @@\n> >>>    \n> >>>    #pragma once\n> >>>    \n> >>> +#include <initializer_list>\n> >>>    #include <iterator>\n> >>>    #include <map>\n> >>>    #include <memory>\n> >>> @@ -237,6 +238,8 @@ public:\n> >>>    \n> >>>    \tValueNode *add(std::unique_ptr<ValueNode> child);\n> >>>    \tValueNode *add(std::string key, std::unique_ptr<ValueNode> child);\n> >>> +\tValueNode *add(std::initializer_list<std::string_view> path,\n> >>> +\t\t       std::unique_ptr<ValueNode> child);\n> >>>    \n> >>>    private:\n> >>>    \tLIBCAMERA_DISABLE_COPY_AND_MOVE(ValueNode)\n> >>> diff --git a/src/libcamera/value_node.cpp b/src/libcamera/value_node.cpp\n> >>> index 50b23284f930..1256adf8361d 100644\n> >>> --- a/src/libcamera/value_node.cpp\n> >>> +++ b/src/libcamera/value_node.cpp\n> >>> @@ -13,6 +13,8 @@\n> >>>    #include <string>\n> >>>    #include <vector>\n> >>>    \n> >>> +#include <libcamera/base/log.h>\n> >>> +#include <libcamera/base/span.h>\n> >>>    #include <libcamera/base/utils.h>\n> >>>    \n> >>>    /**\n> >>> @@ -22,6 +24,8 @@\n> >>>    \n> >>>    namespace libcamera {\n> >>>    \n> >>> +LOG_DEFINE_CATEGORY(ValueNode)\n> >>> +\n> >>>    namespace {\n> >>>    \n> >>>    /* Empty static ValueNode as a safe result for invalid operations */\n> >>> @@ -539,4 +543,56 @@ ValueNode *ValueNode::add(std::string key, std::unique_ptr<ValueNode> child)\n> >>>    \treturn elem.value.get();\n> >>>    }\n> >>>    \n> >>> +/**\n> >>> + * \\brief Add a child node at the given path\n> >>> + * \\param[in] path The path\n> >>> + * \\param[in] child The child node\n> >>> + *\n> >>> + * Add the \\a child node at the given \\a path starting at this node. Missing\n> >>> + * nodes are created along the path. Nodes along the path must be empty, in\n> >>> + * which case they are converted to the Type::Dictionary type, be a dictionary,\n> >>\n> >> It's a small thing, but I'd put the \"in ... type\" part in parenthesis because for\n> >> me it currently disrupts the flow of the list a bit.\n> > \n> > Good idea.\n> > \n> >>> + * or be missing. Otherwise, the \\a child is discarded and the function returns\n> >>> + * a nullptr.\n> >>\n> >> I think the caveat that already created nodes along `path` are left in place\n> >> should be mentioned. Maybe even as a `\\note`.\n> >>\n> >> Or maybe the first non-existent child can be recorded and everything up to that\n> >> can be removed in a `utils::scope_exit` in case of \"failure\". But maybe not worth\n> >> the complexity.\n> > \n> > I'll add a note.\n> > \n> >>> + *\n> >>> + * Path elements are unique in the context of a parent node. If a child with the\n> >>> + * same \\a key already exist at the end of the path, the \\a child is discarded\n> >>> + * and the function returns a nullptr.\n> >>> + *\n> >>> + * \\return A pointer to the \\a child node if successfully added, nullptr\n> >>> + * otherwise\n> >>> + */\n> >>> +ValueNode *ValueNode::add(std::initializer_list<std::string_view> path,\n> >>> +\t\t\t  std::unique_ptr<ValueNode> child)\n> >>\n> >> I think maybe we could use `std::unique_ptr<ValueNode> &&child` and only actually\n> >> move the value if the \"insertion\" will succeed. (Needs the same change in `ValueNode::add()`.)\n> > \n> > Hmmm... I think that would be confusing. The caller would need to do\n> > \n> > \tValueNode &node = ...\n> > \tstd::unique_ptr<ValueNode> child = ...;\n> > \n> > \tnode.add(\"path\", std::move(child));\n> \n> Well, they can do `node.add(\"...\", std::make_unique<ValueNode>(...))` as well.\n> But then the difference is not really visible.\n> \n> > but the move wouldn't be performed in case of failure. Sure, the child\n> > would eventually be freed, but at an unexpected point, and the\n> > std::move() call would be misleading.\n> \n> I suppose it depends on one's expectations about `std::move()`. Technically\n> it is a cast, so it doesn't do anything in and of itself. So for example I\n> think you have the same behaviour if e.g. `vec.push_back(std::move(...))` fails.\n\nYou're right, but I find that confusing. Do you think we should pass an\nrvalue reference to the child as a performance improvement, or because\nthe semantics is better ?\n\n> >>> +{\n> >>> +\tif (!path.size())\n> >>> +\t\treturn NULL;\n> >>\n> >>     nullptr\n> > \n> > Oops.\n> > \n> >>> +\n> >>> +\tValueNode *node = this;\n> >>> +\n> >>> +\tfor (const auto [i, name] : utils::enumerate(path)) {\n> >>> +\t\tauto iter = node->dictionary_.find(name);\n> >>> +\t\tif (iter == node->dictionary_.end()) {\n> >>> +\t\t\tstd::unique_ptr<ValueNode> obj;\n> >>> +\n> >>> +\t\t\tif (i < path.size() - 1)\n> >>> +\t\t\t\tobj = std::make_unique<ValueNode>();\n> >>> +\t\t\telse\n> >>> +\t\t\t\tobj = std::move(child);\n> >>> +\n> >>> +\t\t\tnode = node->add(std::string{ name }, std::move(obj));\n> >>> +\t\t\tif (!node) {\n> >>> +\t\t\t\tSpan<const std::string_view> pathName{ std::data(path), i + 1 };\n> >>> +\t\t\t\tLOG(ValueNode, Error)\n> >>> +\t\t\t\t\t<< \"Failed to populate '\"\n> >>> +\t\t\t\t\t<< utils::join(pathName, \"/\") << \"'\";\n> >>> +\t\t\t\treturn nullptr;\n> >>> +\t\t\t}\n> >>> +\t\t} else {\n> >>> +\t\t\tnode = iter->second;\n> >>> +\t\t}\n> >>> +\t}\n> >>> +\n> >>> +\treturn node;\n> >>> +}\n> >>> +\n> >>>    } /* namespace libcamera */","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 8287CBDCBF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 19 Jan 2026 02:00:49 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 61EAE61FC3;\n\tMon, 19 Jan 2026 03:00:48 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3B47461F61\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 19 Jan 2026 03:00:47 +0100 (CET)","from pendragon.ideasonboard.com (81-175-209-152.bb.dnainternet.fi\n\t[81.175.209.152])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 981D9C1;\n\tMon, 19 Jan 2026 03:00:16 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"nc5+7b8c\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1768788016;\n\tbh=ycX0TztW4OqO3sX1CU7xzNxmsrC+2ilZR3JxQGBA4c4=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=nc5+7b8cF2XrGPsDfy3HraD0YR3Hd89KAyA9ia9/8amM26cK4tXx99quzEqFl1AZ2\n\t4FAV+H6TWg0KiBWoSr/ppKjyauzm67wrbtdA6NH/e/cPAiSohdG2QkwXjhFGUvTlMX\n\tDXkmMkNEhZ/e8JqNbYLsWepbb6UoRuFDFSJ2yYFg=","Date":"Mon, 19 Jan 2026 04:00:24 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","Message-ID":"<20260119020024.GE7759@pendragon.ideasonboard.com>","References":"<20260113000808.15395-1-laurent.pinchart@ideasonboard.com>\n\t<20260113000808.15395-30-laurent.pinchart@ideasonboard.com>\n\t<3544249d-c772-4352-a2aa-28bbbe405dcd@ideasonboard.com>\n\t<20260113135030.GG6198@pendragon.ideasonboard.com>\n\t<50f60673-9c04-4cc9-b9a2-d2a974f16c80@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<50f60673-9c04-4cc9-b9a2-d2a974f16c80@ideasonboard.com>","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":37719,"web_url":"https://patchwork.libcamera.org/comment/37719/","msgid":"<1171d7be-19cd-4b33-8a90-ee142dd99290@ideasonboard.com>","date":"2026-01-19T08:22:49","subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"2026. 01. 19. 3:00 keltezéssel, Laurent Pinchart írta:\n> On Tue, Jan 13, 2026 at 04:09:18PM +0100, Barnabás Pőcze wrote:\n>> 2026. 01. 13. 14:50 keltezéssel, Laurent Pinchart írta:\n>>> On Tue, Jan 13, 2026 at 01:45:22PM +0100, Barnabás Pőcze wrote:\n>>>> 2026. 01. 13. 1:08 keltezéssel, Laurent Pinchart írta:\n>>>>> The GlobalConfiguration class will need to add nested children to a\n>>>>> ValueNode. Add a new overload to the add() function for this purpose.\n>>>>>\n>>>>> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n>>>>> ---\n>>>>>     include/libcamera/internal/value_node.h |  3 ++\n>>>>>     src/libcamera/value_node.cpp            | 56 +++++++++++++++++++++++++\n>>>>>     2 files changed, 59 insertions(+)\n>>>>>\n>>>>> diff --git a/include/libcamera/internal/value_node.h b/include/libcamera/internal/value_node.h\n>>>>> index c285be4957ed..abb0991fd87e 100644\n>>>>> --- a/include/libcamera/internal/value_node.h\n>>>>> +++ b/include/libcamera/internal/value_node.h\n>>>>> @@ -8,6 +8,7 @@\n>>>>>\n>>>>>     #pragma once\n>>>>>\n>>>>> +#include <initializer_list>\n>>>>>     #include <iterator>\n>>>>>     #include <map>\n>>>>>     #include <memory>\n>>>>> @@ -237,6 +238,8 @@ public:\n>>>>>\n>>>>>     \tValueNode *add(std::unique_ptr<ValueNode> child);\n>>>>>     \tValueNode *add(std::string key, std::unique_ptr<ValueNode> child);\n>>>>> +\tValueNode *add(std::initializer_list<std::string_view> path,\n>>>>> +\t\t       std::unique_ptr<ValueNode> child);\n>>>>>\n>>>>>     private:\n>>>>>     \tLIBCAMERA_DISABLE_COPY_AND_MOVE(ValueNode)\n>>>>> diff --git a/src/libcamera/value_node.cpp b/src/libcamera/value_node.cpp\n>>>>> index 50b23284f930..1256adf8361d 100644\n>>>>> --- a/src/libcamera/value_node.cpp\n>>>>> +++ b/src/libcamera/value_node.cpp\n>>>>> @@ -13,6 +13,8 @@\n>>>>>     #include <string>\n>>>>>     #include <vector>\n>>>>>\n>>>>> +#include <libcamera/base/log.h>\n>>>>> +#include <libcamera/base/span.h>\n>>>>>     #include <libcamera/base/utils.h>\n>>>>>\n>>>>>     /**\n>>>>> @@ -22,6 +24,8 @@\n>>>>>\n>>>>>     namespace libcamera {\n>>>>>\n>>>>> +LOG_DEFINE_CATEGORY(ValueNode)\n>>>>> +\n>>>>>     namespace {\n>>>>>\n>>>>>     /* Empty static ValueNode as a safe result for invalid operations */\n>>>>> @@ -539,4 +543,56 @@ ValueNode *ValueNode::add(std::string key, std::unique_ptr<ValueNode> child)\n>>>>>     \treturn elem.value.get();\n>>>>>     }\n>>>>>\n>>>>> +/**\n>>>>> + * \\brief Add a child node at the given path\n>>>>> + * \\param[in] path The path\n>>>>> + * \\param[in] child The child node\n>>>>> + *\n>>>>> + * Add the \\a child node at the given \\a path starting at this node. Missing\n>>>>> + * nodes are created along the path. Nodes along the path must be empty, in\n>>>>> + * which case they are converted to the Type::Dictionary type, be a dictionary,\n>>>>\n>>>> It's a small thing, but I'd put the \"in ... type\" part in parenthesis because for\n>>>> me it currently disrupts the flow of the list a bit.\n>>>\n>>> Good idea.\n>>>\n>>>>> + * or be missing. Otherwise, the \\a child is discarded and the function returns\n>>>>> + * a nullptr.\n>>>>\n>>>> I think the caveat that already created nodes along `path` are left in place\n>>>> should be mentioned. Maybe even as a `\\note`.\n>>>>\n>>>> Or maybe the first non-existent child can be recorded and everything up to that\n>>>> can be removed in a `utils::scope_exit` in case of \"failure\". But maybe not worth\n>>>> the complexity.\n>>>\n>>> I'll add a note.\n>>>\n>>>>> + *\n>>>>> + * Path elements are unique in the context of a parent node. If a child with the\n>>>>> + * same \\a key already exist at the end of the path, the \\a child is discarded\n>>>>> + * and the function returns a nullptr.\n>>>>> + *\n>>>>> + * \\return A pointer to the \\a child node if successfully added, nullptr\n>>>>> + * otherwise\n>>>>> + */\n>>>>> +ValueNode *ValueNode::add(std::initializer_list<std::string_view> path,\n>>>>> +\t\t\t  std::unique_ptr<ValueNode> child)\n>>>>\n>>>> I think maybe we could use `std::unique_ptr<ValueNode> &&child` and only actually\n>>>> move the value if the \"insertion\" will succeed. (Needs the same change in `ValueNode::add()`.)\n>>>\n>>> Hmmm... I think that would be confusing. The caller would need to do\n>>>\n>>> \tValueNode &node = ...\n>>> \tstd::unique_ptr<ValueNode> child = ...;\n>>>\n>>> \tnode.add(\"path\", std::move(child));\n>>\n>> Well, they can do `node.add(\"...\", std::make_unique<ValueNode>(...))` as well.\n>> But then the difference is not really visible.\n>>\n>>> but the move wouldn't be performed in case of failure. Sure, the child\n>>> would eventually be freed, but at an unexpected point, and the\n>>> std::move() call would be misleading.\n>>\n>> I suppose it depends on one's expectations about `std::move()`. Technically\n>> it is a cast, so it doesn't do anything in and of itself. So for example I\n>> think you have the same behaviour if e.g. `vec.push_back(std::move(...))` fails.\n> \n> You're right, but I find that confusing. Do you think we should pass an\n> rvalue reference to the child as a performance improvement, or because\n> the semantics is better ?\n\nI like the semantics better, it is more \"transactional\": if anything goes wrong,\nit is as if the function has never been called. But I suppose this is a very\nminor thing, and this is an internal function, so it can be easily modified if\na real use case presents itself.\n\n\n> \n>>>>> +{\n>>>>> +\tif (!path.size())\n>>>>> +\t\treturn NULL;\n>>>>\n>>>>      nullptr\n>>>\n>>> Oops.\n>>>\n>>>>> +\n>>>>> +\tValueNode *node = this;\n>>>>> +\n>>>>> +\tfor (const auto [i, name] : utils::enumerate(path)) {\n>>>>> +\t\tauto iter = node->dictionary_.find(name);\n>>>>> +\t\tif (iter == node->dictionary_.end()) {\n>>>>> +\t\t\tstd::unique_ptr<ValueNode> obj;\n>>>>> +\n>>>>> +\t\t\tif (i < path.size() - 1)\n>>>>> +\t\t\t\tobj = std::make_unique<ValueNode>();\n>>>>> +\t\t\telse\n>>>>> +\t\t\t\tobj = std::move(child);\n>>>>> +\n>>>>> +\t\t\tnode = node->add(std::string{ name }, std::move(obj));\n>>>>> +\t\t\tif (!node) {\n>>>>> +\t\t\t\tSpan<const std::string_view> pathName{ std::data(path), i + 1 };\n>>>>> +\t\t\t\tLOG(ValueNode, Error)\n>>>>> +\t\t\t\t\t<< \"Failed to populate '\"\n>>>>> +\t\t\t\t\t<< utils::join(pathName, \"/\") << \"'\";\n>>>>> +\t\t\t\treturn nullptr;\n>>>>> +\t\t\t}\n>>>>> +\t\t} else {\n>>>>> +\t\t\tnode = iter->second;\n>>>>> +\t\t}\n>>>>> +\t}\n>>>>> +\n>>>>> +\treturn node;\n>>>>> +}\n>>>>> +\n>>>>>     } /* namespace libcamera */\n> \n> --\n> Regards,\n> \n> Laurent Pinchart","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 79050BDCBF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 19 Jan 2026 08:22:57 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A228C61FB9;\n\tMon, 19 Jan 2026 09:22:56 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AFDAE61F9F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 19 Jan 2026 09:22:54 +0100 (CET)","from [192.168.33.17] (185.221.143.114.nat.pool.zt.hu\n\t[185.221.143.114])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 3D4BD2D9;\n\tMon, 19 Jan 2026 09:22:24 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"JTyCeMkE\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1768810944;\n\tbh=GCL+D/JPwhuYG5YCrX0Mxz4ajrGfvm9naGdsS+BdIO8=;\n\th=Date:Subject:To:Cc:References:From:In-Reply-To:From;\n\tb=JTyCeMkEUkxXusvXiJ5etr5LDOHu6ftUCeW9OQfSBhrCsweVj+3iPiOk393TDnDoW\n\tjfgLr7GpVKzMTLdymyBYXCi552mVtH8Yxh/kDpGJ5sUvxvrYTZPRkymGw/Pp+vW4eM\n\t9vzecJ41pVBXdojQvzjJloMy7ZN1GLhUVwLJ4U5Y=","Message-ID":"<1171d7be-19cd-4b33-8a90-ee142dd99290@ideasonboard.com>","Date":"Mon, 19 Jan 2026 09:22:49 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 29/36] libcamera: value_node: Support adding nested\n\tchildren in one operation","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>, =?utf-8?q?Barnab?=\n\t=?utf-8?b?w6FzIFDFkWN6ZQ==?= <barnabas.pocze@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","References":"<20260113000808.15395-1-laurent.pinchart@ideasonboard.com>\n\t<20260113000808.15395-30-laurent.pinchart@ideasonboard.com>\n\t<3544249d-c772-4352-a2aa-28bbbe405dcd@ideasonboard.com>\n\t<20260113135030.GG6198@pendragon.ideasonboard.com>\n\t<50f60673-9c04-4cc9-b9a2-d2a974f16c80@ideasonboard.com>\n\t<7peJHTz3XRdFPldkuwTmK6TQiZ0ZknL8qzxemzZA0jxNT9ssYkCkBIY-f___M7mNsQ5Gcro_EYF3k5a6gpIQZQ==@protonmail.internalid>\n\t<20260119020024.GE7759@pendragon.ideasonboard.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<20260119020024.GE7759@pendragon.ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]