[RFC v2 3/4] libcamera: Implement YamlEmitter
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Sun Oct 20 23:09:17 CEST 2024
Hi Jacopo,
Thank you for the patch.
On Thu, Oct 17, 2024 at 02:52:18PM +0200, Jacopo Mondi wrote:
> Signed-off-by: Jacopo Mondi <jacopo.mondi at ideasonboard.com>
> ---
> include/libcamera/internal/meson.build | 1 +
> include/libcamera/internal/yaml_emitter.h | 162 ++++++++++
> src/libcamera/meson.build | 1 +
> src/libcamera/yaml_emitter.cpp | 362 ++++++++++++++++++++++
> 4 files changed, 526 insertions(+)
> create mode 100644 include/libcamera/internal/yaml_emitter.h
> create mode 100644 src/libcamera/yaml_emitter.cpp
>
> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
> index 1c5eef9cab80..7533b075fde2 100644
> --- a/include/libcamera/internal/meson.build
> +++ b/include/libcamera/internal/meson.build
> @@ -41,6 +41,7 @@ libcamera_internal_headers = files([
> 'v4l2_pixelformat.h',
> 'v4l2_subdevice.h',
> 'v4l2_videodevice.h',
> + 'yaml_emitter.h',
> 'yaml_parser.h',
> ])
>
> diff --git a/include/libcamera/internal/yaml_emitter.h b/include/libcamera/internal/yaml_emitter.h
> new file mode 100644
> index 000000000000..e4a0e3b440a5
> --- /dev/null
> +++ b/include/libcamera/internal/yaml_emitter.h
> @@ -0,0 +1,162 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2024, Ideas On Board Oy
> + *
> + * libcamera YAML emitter helper
> + */
> +
> +#pragma once
> +
> +#include <memory>
> +#include <string_view>
> +
> +#include <libcamera/base/class.h>
> +#include <libcamera/base/file.h>
> +#include <libcamera/orientation.h>
> +
> +#include <yaml.h>
> +
> +namespace libcamera {
> +
> +class YamlDict;
> +class YamlEvent;
> +class YamlList;
> +class YamlRoot;
> +class YamlScalar;
> +
> +class YamlEmitter final
> +{
> +public:
> + ~YamlEmitter();
> +
> + static std::unique_ptr<YamlRoot> root(std::string_view path);
> +
> + int emit();
> + yaml_event_t *event() { return &event_; }
> +
> +private:
> + LIBCAMERA_DISABLE_COPY(YamlEmitter)
> +
> + class Emitter
> + {
> + public:
> + ~Emitter();
> +
> + void init(File *file);
> +
> + int emit(yaml_event_t *event);
> +
> + private:
> + void logError();
> +
> + yaml_emitter_t emitter_;
> + };
> +
> + YamlEmitter() = default;
> +
> + void init();
> +
> + std::unique_ptr<File> file_;
No need for a dynamic allocation.
File file_;
> + yaml_event_t event_;
> + Emitter emitter_;
> +};
> +
> +class YamlOutput
> +{
> +public:
> + virtual ~YamlOutput() {};
> +
> + YamlOutput(YamlOutput &&other)
> + {
> + emitter_ = other.emitter_;
> + other.emitter_ = nullptr;
> + }
> +
> + YamlScalar scalar();
> + std::unique_ptr<YamlDict> dict();
> + std::unique_ptr<YamlList> list();
You still allocate everything on the heap. Perhaps with the exception of
the YamlEmitter argument to the YamlRoot constructor, there should be no
usage of std::unique_ptr<> anywhere in this header. Heap allocations are
costly, and I think we should be able to use stack allocations only.
You will need to define default constructors, as well as move assignment
operators. Be careful about the YamlOutput::event_ member, it's a C
structure so it won't have a move constructor or assignment operator.
Moving it trivially by copying the contents may cause issues if anything
holds a pointer to the yaml_event_t within libyaml.
> +
> +protected:
> + YamlOutput(YamlEmitter *emitter)
> + : emitter_(emitter)
> + {
> + }
> +
> + int emitScalar(std::string_view scalar);
> + int emitMappingStart();
> + int emitMappingEnd();
> + int emitSequenceStart();
> + int emitSequenceEnd();
> +
> + YamlEmitter *emitter_;
> + yaml_event_t event_;
> +};
> +
> +class YamlRoot : public YamlOutput
> +{
> +public:
> + ~YamlRoot();
> +
> + std::unique_ptr<YamlList> list();
> + std::unique_ptr<YamlDict> dict();
> + void scalar(std::string_view scalar);
> +
> +private:
> + friend class YamlEmitter;
> +
> + YamlRoot(YamlEmitter *emitter)
> + : YamlOutput(emitter)
> + {
> + emitterRoot_ = std::unique_ptr<YamlEmitter>(emitter);
> + }
> +
> + std::unique_ptr<YamlEmitter> emitterRoot_;
> +};
> +
> +class YamlScalar : public YamlOutput
> +{
> +public:
> + ~YamlScalar() = default;
> +
> + void operator=(std::string_view scalar);
> +
> +private:
> + friend class YamlOutput;
> +
> + YamlScalar(YamlEmitter *emitter);
> +};
> +
> +class YamlList : public YamlOutput
> +{
> +public:
> + YamlList(YamlList &&other) = default;
> + ~YamlList();
> +
> + std::unique_ptr<YamlList> list();
> + std::unique_ptr<YamlDict> dict();
> + void scalar(std::string_view scalar);
> +
> +private:
> + friend class YamlOutput;
> +
> + YamlList(YamlEmitter *emitter);
> +};
> +
> +class YamlDict : public YamlOutput
> +{
> +public:
> + YamlDict(YamlDict &&other) = default;
> + ~YamlDict();
> +
> + std::unique_ptr<YamlList> list(std::string_view key);
> + std::unique_ptr<YamlDict> dict(std::string_view key);
> +
> + YamlScalar operator[](std::string_view key);
> +
> +private:
> + friend class YamlOutput;
> +
> + YamlDict(YamlEmitter *emitter);
> +};
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index aa9ab0291854..5b8af4103085 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -51,6 +51,7 @@ libcamera_internal_sources = files([
> 'v4l2_pixelformat.cpp',
> 'v4l2_subdevice.cpp',
> 'v4l2_videodevice.cpp',
> + 'yaml_emitter.cpp',
> 'yaml_parser.cpp',
> ])
>
> diff --git a/src/libcamera/yaml_emitter.cpp b/src/libcamera/yaml_emitter.cpp
> new file mode 100644
> index 000000000000..f2aaa1c1c1a6
> --- /dev/null
> +++ b/src/libcamera/yaml_emitter.cpp
> @@ -0,0 +1,362 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2024, Ideas On Board Oy
> + *
> + * libcamera YAML emitter helper
> + */
> +
> +#include "libcamera/internal/yaml_emitter.h"
> +
> +#include <libcamera/base/log.h>
> +
> +/**
> + * \file yaml_emitter.h
> + * \brief A YAML emitter helper
> + *
> + * The YAML Emitter helpers allows users to emit output in YAML format.
> + *
> + * To emit YAML users of this classes should create a root node with
> + *
> + * \code
> + std::string filePath("...");
> + auto root = YamlEmitter::root(filePath);
> + \endcode
> + *
> + * A YamlRoot implements RAII-style handling of YAML sequence and document
> + * events handling. Creating a YamlRoot emits the STREAM_START and DOC_START
> + * events. Once a YamlRoot gets deleted the DOC_SEND and STREAM_END events
> + * gets automatically deleted.
> + *
> + * Once a root node has been created it is possible to populate it with
> + * scalars, list or dictionaries.
> + *
> + * YamlList and YamlDict can only be created wrapped in unique_ptr<> instances,
> + * to implement a RAII-style handling of YAML of lists and dictionaries.
> + * Creating a YamlList and a YamlDict emits the YAML sequence and mapping start
> + * events. Once an instance gets deleted, the sequence and mapping gets
> + * automatically emitted.
> + *
> + * A YamlScalar is a simpler object that can be assigned to different types
> + * to emit them as strings.
> + */
> +
> +namespace libcamera {
> +
> +LOG_DEFINE_CATEGORY(YamlEmitter)
> +
> +namespace {
> +
> +int yamlWrite(void *data, unsigned char *buffer, size_t size)
> +{
> + File *file = static_cast<File *>(data);
> +
> + Span<unsigned char> buf{ buffer, size };
> + ssize_t ret = file->write(buf);
> + if (ret < 0) {
> + ret = errno;
> + LOG(YamlEmitter, Error) << "Write error : " << strerror(ret);
> + return 0;
> + }
> +
> + return 1;
> +}
> +
> +} /* namespace */
> +
> +/**
> + * \class YamlEmitter
> + *
> + * Yaml Emitter entry point. Allows to create a YamlRoot object that users
> + * can populate.
> + */
> +
> +YamlEmitter::~YamlEmitter()
> +{
> + yaml_event_delete(&event_);
> +}
> +
> +/**
> + * \brief Create a YamlRoot that applications can start populating with YamlOutput
> + * \param[in] path The YAML output file path
> + * \return A unique pointer to a YamlRoot
> + */
> +std::unique_ptr<YamlRoot> YamlEmitter::root(std::string_view path)
> +{
> + YamlEmitter *emitter = new YamlEmitter();
> +
> + std::string filePath(path);
> + emitter->file_ = std::make_unique<File>(filePath);
> + emitter->file_->open(File::OpenModeFlag::WriteOnly);
> +
> + emitter->init();
> +
> + YamlRoot *root = new YamlRoot(emitter);
> + return std::unique_ptr<YamlRoot>(root);
> +}
> +
> +/**
> + * \brief Emit a yaml event
> + */
> +int YamlEmitter::emit()
> +{
> + return emitter_.emit(&event_);
> +}
> +
> +void YamlEmitter::init()
> +{
> + emitter_.init(file_.get());
> +
> + yaml_stream_start_event_initialize(&event_, YAML_UTF8_ENCODING);
> + emitter_.emit(&event_);
> +
> + yaml_document_start_event_initialize(&event_, NULL, NULL, NULL, 0);
> + emitter_.emit(&event_);
> +}
> +
> +/**
> + * \class YamlEmitter::Emitter
> + *
> + * yaml_emitter_t wrapper. Initialize the yaml_emitter_t on creation allows
> + * YamlOutput classes to emit events.
> + */
> +
> +YamlEmitter::Emitter::~Emitter()
> +{
> + yaml_emitter_delete(&emitter_);
> +}
> +
> +void YamlEmitter::Emitter::init(File *file)
> +{
> + yaml_emitter_initialize(&emitter_);
> + yaml_emitter_set_output(&emitter_, yamlWrite, file);
> +}
> +
> +void YamlEmitter::Emitter::logError()
> +{
> + switch (emitter_.error) {
> + case YAML_MEMORY_ERROR:
> + LOG(YamlEmitter, Error)
> + << "Memory error: Not enough memory for emitting";
> + break;
> +
> + case YAML_WRITER_ERROR:
> + LOG(YamlEmitter, Error)
> + << "Writer error: " << emitter_.problem;
> + break;
> +
> + case YAML_EMITTER_ERROR:
> + LOG(YamlEmitter, Error)
> + << "Emitter error: " << emitter_.problem;
> + break;
> +
> + default:
> + LOG(YamlEmitter, Error) << "Internal problem";
> + break;
> + }
> +}
> +
> +int YamlEmitter::Emitter::emit(yaml_event_t *event)
> +{
> + int ret = yaml_emitter_emit(&emitter_, event);
> + if (!ret) {
> + logError();
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * \class YamlOutput
> + *
> + * The YamlOutput base class. From this class YamlScalar, YamlList and YamlDict
> + * are derived.
> + *
> + * The YamlOutput class provides functions to create a scalar, a list or a
> + * dictionary.
> + *
> + * The class cannot be instantiated directly by applications.
> + */
> +
> +YamlScalar YamlOutput::scalar()
> +{
> + return YamlScalar(emitter_);
> +}
> +
> +std::unique_ptr<YamlDict> YamlOutput::dict()
> +{
> + YamlDict dict(emitter_);
> +
> + return std::make_unique<YamlDict>(std::move(dict));
> +}
> +
> +std::unique_ptr<YamlList> YamlOutput::list()
> +{
> + YamlList list(emitter_);
> +
> + return std::make_unique<YamlList>(std::move(list));
> +}
> +
> +int YamlOutput::emitScalar(std::string_view scalar)
> +{
> + const unsigned char *value = reinterpret_cast<const unsigned char *>
> + (scalar.data());
> + yaml_scalar_event_initialize(emitter_->event(), NULL, NULL, value,
> + scalar.length(), true, false,
> + YAML_PLAIN_SCALAR_STYLE);
> + return emitter_->emit();
> +}
> +
> +int YamlOutput::emitMappingStart()
> +{
> + yaml_mapping_start_event_initialize(emitter_->event(), NULL, NULL,
> + true, YAML_BLOCK_MAPPING_STYLE);
> + return emitter_->emit();
> +}
> +
> +int YamlOutput::emitMappingEnd()
> +{
> + yaml_mapping_end_event_initialize(emitter_->event());
> + return emitter_->emit();
> +}
> +
> +int YamlOutput::emitSequenceStart()
> +{
> + yaml_sequence_start_event_initialize(emitter_->event(), NULL, NULL, true,
> + YAML_BLOCK_SEQUENCE_STYLE);
> + return emitter_->emit();
> +}
> +
> +int YamlOutput::emitSequenceEnd()
> +{
> + yaml_sequence_end_event_initialize(emitter_->event());
> + return emitter_->emit();
> +}
> +
> +/**
> + * \class YamlRoot
> + *
> + * Yaml root node. A root node can be populated with a scalar, a list or a dict.
> + */
> +
> +YamlRoot::~YamlRoot()
> +{
> + yaml_document_end_event_initialize(emitter_->event(), 0);
> + emitterRoot_->emit();
> +
> + yaml_stream_end_event_initialize(emitter_->event());
> + emitterRoot_->emit();
> +}
> +
> +std::unique_ptr<YamlDict> YamlRoot::dict()
> +{
> + emitMappingStart();
> +
> + return YamlOutput::dict();
> +}
> +
> +std::unique_ptr<YamlList> YamlRoot::list()
> +{
> + emitSequenceStart();
> +
> + return YamlOutput::list();
> +}
> +
> +/**
> + * \class YamlScalar
> + *
> + * A YamlScalar can be assigned to an std::string_view and other libcamera
> + * types to emit them as YAML scalars.
> + */
> +
> +YamlScalar::YamlScalar(YamlEmitter *emitter)
> + : YamlOutput(emitter)
> +{
> +}
> +
> +void YamlScalar::operator=(std::string_view scalar)
> +{
> + emitScalar(scalar);
> +}
> +
> +/**
> + * \class YamlList
> + *
> + * A YamlList can be populated with scalar and allows to create other lists
> + * and dictionaries.
> + */
> +
> +YamlList::YamlList(YamlEmitter *emitter)
> + : YamlOutput(emitter)
> +{
> +}
> +
> +YamlList::~YamlList()
> +{
> + if (emitter_)
> + emitSequenceEnd();
> +}
> +
> +void YamlList::scalar(std::string_view scalar)
> +{
> + emitScalar(scalar);
> +}
> +
> +std::unique_ptr<YamlList> YamlList::list()
> +{
> + emitSequenceStart();
> +
> + return YamlOutput::list();
> +}
> +
> +std::unique_ptr<YamlDict> YamlList::dict()
> +{
> + emitMappingStart();
> +
> + return YamlOutput::dict();
> +}
> +
> +/**
> + * \class YamlDict
> + *
> + * A YamlDict derives an unordered_map that maps strings to YAML scalar.
> + *
> + * A YamlDict can be populated with scalars using operator[] and allows to
> + * create other lists and dictionaries.
> + */
> +
> +YamlDict::YamlDict(YamlEmitter *emitter)
> + : YamlOutput(emitter)
> +{
> +}
> +
> +YamlDict::~YamlDict()
> +{
> + if (emitter_)
> + emitMappingEnd();
> +}
> +
> +std::unique_ptr<YamlList> YamlDict::list(std::string_view key)
> +{
> + emitScalar(key);
> + emitSequenceStart();
> +
> + return YamlOutput::list();
> +}
> +
> +std::unique_ptr<YamlDict> YamlDict::dict(std::string_view key)
> +{
> + emitScalar(key);
> + emitMappingStart();
> +
> + return YamlOutput::dict();
> +}
> +
> +YamlScalar YamlDict::operator[](std::string_view key)
> +{
> + emitScalar(key);
> +
> + return YamlOutput::scalar();
> +}
> +
> +} /* namespace libcamera */
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list