[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