H5SingleDataReader.hpp 3.29 KB
#pragma once

#include <algorithm>
#include <array>
#include <cassert>
#include <memory>
#include <mutex>
#include <numeric>
#include <string>

#include <H5Cpp.h>

/*
 * HDF5 C++ API doesn't support multiple threads, even instantiation of types
 * since it uses global structres.
 *
 * Thus, to avoid deafult construction of member H5 types, they are stored using
 * not owning pointers.
 */
class H5SingleDataReader {
  public:
	H5SingleDataReader(const H5SingleDataReader &) = delete;
	H5SingleDataReader &operator=(const H5SingleDataReader &) = delete;

	H5SingleDataReader(H5SingleDataReader &&) noexcept = default;
	H5SingleDataReader &operator=(H5SingleDataReader &&) noexcept = default;

	std::array<hsize_t, 3> dimensions{0, 0, 0};

	H5SingleDataReader(const std::string &path, const std::string &dataSetPath,
	                   const H5::PredType &dataType, int outputDimension) {
		std::lock_guard<std::mutex> guard(lock);

		type.reset(assign(dataType));

		const H5::H5File file(path, H5F_ACC_RDONLY);
		dataSet.reset(assign(file.openDataSet(dataSetPath)));
		fileSpace.reset(assign(dataSet->getSpace()));

		[[maybe_unused]] const auto rank =
		    fileSpace->getSimpleExtentDims(dimensions.data(), nullptr);
		assert(outputDimension <= rank);

		const std::array<hsize_t, 1> memoryDimensions{std::accumulate(
		    dimensions.cbegin(), dimensions.cbegin() + outputDimension, 1LLU,
		    std::multiplies<>())};
		const std::array<hsize_t, 1> memoryOffset{0};
		memorySpace.reset(assign(H5::DataSpace(1, memoryDimensions.data())));
		memorySpace->selectHyperslab(H5S_SELECT_SET, memoryDimensions.data(),
		                             memoryOffset.data());

		totalSize = memorySpace->getSelectNpoints();
	}

	hsize_t size() const { return totalSize; }

	void read(void *buffer) const {
		std::lock_guard<std::mutex> guard(lock);
		dataSet->read(buffer, *type, *memorySpace, *fileSpace);
	}

	void select(const std::array<hsize_t, 3> selectionSize,
	            const std::array<hsize_t, 3> selectionOffset) const {
		std::lock_guard<std::mutex> guard(lock);
		fileSpace->selectHyperslab(H5S_SELECT_SET, selectionSize.data(),
		                           selectionOffset.data());
	}

	virtual ~H5SingleDataReader() {
		std::lock_guard<std::mutex> guard(lock);
		delete memorySpace.get();
		delete fileSpace.get();
		delete dataSet.get();
		delete type.get();
	}

	const static H5::PredType UINT8;
	const static H5::PredType UINT16;
	const static H5::PredType UINT32;
	const static H5::PredType UINT64;
	const static H5::PredType INT8;
	const static H5::PredType INT16;
	const static H5::PredType INT32;
	const static H5::PredType INT64;
	const static H5::PredType FLOAT;
	const static H5::PredType DOUBLE;

  private:
	static std::mutex lock;

	hsize_t totalSize;

	struct NoOp {
		template <typename T> void operator()(T const &) const noexcept {}
	};

	/**
	 * not_owning_ptr allows to use default move constructor
	 * and manually manage target memory
	 */
	template <typename T> using not_owning_ptr = std::unique_ptr<T, NoOp>;

	not_owning_ptr<const H5::PredType> type;
	not_owning_ptr<const H5::DataSet> dataSet;
	not_owning_ptr<const H5::DataSpace> fileSpace;
	not_owning_ptr<const H5::DataSpace> memorySpace;

	template <typename T> static const T *assign(const T &value) {
		T *const result = new T(value);
		*result = value;
		return result;
	}
};