diff --git a/CMakeLists.txt b/CMakeLists.txt
index 21ac656880..d26b6b6ac0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -402,6 +402,7 @@ include(${openPMD_SOURCE_DIR}/cmake/dependencies/pybind11.cmake)
set(CORE_SOURCE
src/config.cpp
src/ChunkInfo.cpp
+ src/CustomHierarchy.cpp
src/Dataset.cpp
src/Datatype.cpp
src/Error.cpp
@@ -866,6 +867,7 @@ if(openPMD_BUILD_TESTING)
list(APPEND ${out_list}
test/Files_Core/automatic_variable_encoding.cpp
test/Files_Core/read_nonexistent_attribute.cpp
+ test/Files_Core/custom_hierarchy.cpp
)
endif()
endmacro()
diff --git a/include/openPMD/CustomHierarchy.hpp b/include/openPMD/CustomHierarchy.hpp
new file mode 100644
index 0000000000..6a325e96ab
--- /dev/null
+++ b/include/openPMD/CustomHierarchy.hpp
@@ -0,0 +1,190 @@
+/* Copyright 2023 Franz Poeschel
+ *
+ * This file is part of openPMD-api.
+ *
+ * openPMD-api is free software: you can redistribute it and/or modify
+ * it under the terms of of either the GNU General Public License or
+ * the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * openPMD-api is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License and the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * and the GNU Lesser General Public License along with openPMD-api.
+ * If not, see .
+ */
+#pragma once
+
+#include "openPMD/Error.hpp"
+#include "openPMD/IO/AbstractIOHandler.hpp"
+#include "openPMD/Mesh.hpp"
+#include "openPMD/ParticleSpecies.hpp"
+#include "openPMD/RecordComponent.hpp"
+#include "openPMD/backend/Attributable.hpp"
+#include "openPMD/backend/Container.hpp"
+#include "openPMD/backend/Writable.hpp"
+
+#include
+#include
+#include
+#include
+
+namespace openPMD
+{
+class CustomHierarchy;
+namespace internal
+{
+ using CustomHierarchyData = ContainerData;
+} // namespace internal
+
+class CustomHierarchy;
+
+/*
+ * This is its own class, so the return value of asContainerOf() is also
+ * convsertible again.
+ */
+template
+class ConvertibleContainer : public Container
+{
+ template
+ friend class ConversibleContainer;
+ friend class CustomHierarchy;
+
+protected:
+ using Container_t = Container;
+ using Data_t = internal::ContainerData;
+ static_assert(
+ std::is_base_of_v);
+
+ using Container_t::Container_t;
+
+private:
+ explicit ConvertibleContainer() = default;
+
+public:
+ auto asDataset() -> RecordComponent;
+ // TODO also: asContainerOf()
+};
+
+namespace traits
+{
+ template <>
+ struct DeferredInitPolicy>
+ {
+ template
+ static void call(Container_const_or_not &);
+ };
+
+ template <>
+ struct GenerationPolicy
+ {
+ constexpr static bool is_noop = false;
+ template
+ void operator()(Container &cont, Iterator &it)
+ {
+ auto &writable = it->second.writable();
+
+ // These should be different
+ auto child_shared_data = &it->second.Attributable::get();
+ auto parent_shared_data = &cont.Attributable::get();
+ if (child_shared_data == parent_shared_data)
+ {
+ throw std::runtime_error(
+ "Trying to emplace object as its own child");
+ }
+
+ // These might be different, but might also be the same
+ //
+ // For an explanation, ref. the documentation of
+ // Writable::attributable: This is a pointer back to the first
+ // created Attributable instance linking this Writable. There might
+ // be multiple Attributable objects linking the same backend
+ // Writable object when opening multiple "views" on the same backend
+ // object, e.g. when a scalar Record is at the same time a
+ // RecordComponent, or when reopening an object as a
+ // CustomHierarchy.
+ //
+ // Since CustomHierarchy performs no memory management by default,
+ // we must ensure that the backpointer in Writable::attributable
+ // remains valid when the frontend instance pointed by
+ // Writable::attributable *is* the CustomHierarchy instance (happens
+ // when it is the first frontend object created for that backend
+ // object).
+ auto backpointer = writable.attributable;
+ auto emplaced_pointer = it->second.m_attri.get();
+ auto *group_metadata =
+ (**cont.m_attri).m_writable.objectType.requireGroup();
+ if (backpointer == emplaced_pointer)
+ {
+ group_metadata
+ ->m_children_managed_as_custom_hierarchy[it->first] =
+ // NO move!! The iterator must stay alive
+ std::make_shared(it->second);
+ }
+ }
+ };
+} // namespace traits
+
+// If we want to break the DeferredInitPolicy logic, we may alternatively derive
+// this privately from ConvertibleContainer, and then overload
+// access operators to return CustomHierarchy instances
+class CustomHierarchy : public ConvertibleContainer
+{
+ friend class Iteration;
+ friend class Container;
+ friend class Attributable;
+ friend struct traits::DeferredInitPolicy>;
+
+private:
+ using Parent_t = ConvertibleContainer;
+ using Container_t = typename Parent_t::Container_t;
+ using Data_t = typename Parent_t::Data_t;
+
+protected:
+ CustomHierarchy(NoInit);
+ CustomHierarchy(std::shared_ptr other);
+ CustomHierarchy(Attributable const &other);
+
+ void read(std::vector ¤tPath);
+
+ void flush_internal(
+ internal::FlushParams const &, std::vector currentPath);
+ void flush(std::string const &path, internal::FlushParams const &) override;
+
+ /**
+ * @brief Link with parent.
+ *
+ * @param w The Writable representing the parent.
+ */
+ void linkHierarchy(Writable &w) override;
+
+public:
+ CustomHierarchy();
+
+ CustomHierarchy(CustomHierarchy const &other) = default;
+ CustomHierarchy(CustomHierarchy &&other) = default;
+
+ CustomHierarchy &operator=(CustomHierarchy const &) = default;
+ CustomHierarchy &operator=(CustomHierarchy &&) = default;
+
+ // TODO should we automatically read upon returning / instantiating a
+ // CustomHierarchy object? i.e. upon Attributable::customHierarchy() and
+ // CustomHierarchy::operator[]().
+ //
+ // set max_recursion_depth = 0 for infinite cycling
+ // recursion depth includes the current object
+ // recursion will not continue expanding into regions that are already known
+ // (hence not transitively expand into unknown subregions of known regions)
+ auto read(size_t max_recursion_depth = 1) -> CustomHierarchy;
+
+ void printRecursively();
+
+private:
+ void printRecursively(std::string indent);
+};
+} // namespace openPMD
diff --git a/include/openPMD/IO/AbstractIOHandler.hpp b/include/openPMD/IO/AbstractIOHandler.hpp
index 9b7735b5ba..b54b661c78 100644
--- a/include/openPMD/IO/AbstractIOHandler.hpp
+++ b/include/openPMD/IO/AbstractIOHandler.hpp
@@ -26,6 +26,7 @@
#include "openPMD/IterationEncoding.hpp"
#include "openPMD/config.hpp"
#include "openPMD/version.hpp"
+#include
#if openPMD_HAVE_MPI
#include
@@ -81,6 +82,66 @@ enum class FlushLevel
CreateOrOpenFiles
};
+std::ostream &operator<<(std::ostream &, FlushLevel);
+
+namespace flush_level
+{
+ inline constexpr auto global_flushpoint(FlushLevel fl)
+ {
+ switch (fl)
+ {
+ case FlushLevel::UserFlush:
+ return true;
+ case FlushLevel::InternalFlush:
+ case FlushLevel::SkeletonOnly:
+ case FlushLevel::CreateOrOpenFiles:
+ return false;
+ }
+ return false; // unreachable
+ }
+ // same as global_flushpoint for now, but we will soon introduce
+ // immediate_flush
+ inline constexpr auto write_datasets(FlushLevel fl)
+ {
+ switch (fl)
+ {
+ case FlushLevel::UserFlush:
+ return true;
+ case FlushLevel::InternalFlush:
+ case FlushLevel::SkeletonOnly:
+ case FlushLevel::CreateOrOpenFiles:
+ return false;
+ }
+ return false; // unreachable
+ }
+ inline constexpr auto write_attributes(FlushLevel fl)
+ {
+ switch (fl)
+ {
+ case FlushLevel::UserFlush:
+ case FlushLevel::InternalFlush:
+ return true;
+ case FlushLevel::SkeletonOnly:
+ case FlushLevel::CreateOrOpenFiles:
+ return false;
+ }
+ return false; // unreachable
+ }
+ inline constexpr auto flush_hierarchy(FlushLevel fl)
+ {
+ switch (fl)
+ {
+ case FlushLevel::UserFlush:
+ case FlushLevel::InternalFlush:
+ case FlushLevel::SkeletonOnly:
+ return true;
+ case FlushLevel::CreateOrOpenFiles:
+ return false;
+ }
+ return false; // unreachable
+ }
+} // namespace flush_level
+
enum class OpenpmdStandard
{
v_1_0_0,
@@ -121,6 +182,7 @@ namespace internal
* To be used for reading
*/
FlushParams const defaultFlushParams{};
+ FlushParams const publicFlush{FlushLevel::UserFlush};
struct ParsedFlushParams;
diff --git a/include/openPMD/IO/AbstractIOHandlerImpl.hpp b/include/openPMD/IO/AbstractIOHandlerImpl.hpp
index d45ce1bdcc..fdb8af3599 100644
--- a/include/openPMD/IO/AbstractIOHandlerImpl.hpp
+++ b/include/openPMD/IO/AbstractIOHandlerImpl.hpp
@@ -39,7 +39,7 @@ class AbstractIOHandlerImpl
virtual ~AbstractIOHandlerImpl() = default;
- std::future flush();
+ std::future flush(FlushLevel);
/**
* Close the file corresponding with the writable and release file handles.
diff --git a/include/openPMD/IO/IOTask.hpp b/include/openPMD/IO/IOTask.hpp
index 25e0d6ad54..a8efab571e 100644
--- a/include/openPMD/IO/IOTask.hpp
+++ b/include/openPMD/IO/IOTask.hpp
@@ -35,6 +35,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -89,6 +90,8 @@ OPENPMDAPI_EXPORT_ENUM_CLASS(Operation){
}; // note: if you change the enum members here, please update
// docs/source/dev/design.rst
+std::ostream &operator<<(std::ostream &os, Operation op);
+
namespace internal
{
/*
diff --git a/include/openPMD/IO/JSON/JSONIOHandlerImpl.hpp b/include/openPMD/IO/JSON/JSONIOHandlerImpl.hpp
index 6df0c60ced..3e0758aee2 100644
--- a/include/openPMD/IO/JSON/JSONIOHandlerImpl.hpp
+++ b/include/openPMD/IO/JSON/JSONIOHandlerImpl.hpp
@@ -241,7 +241,7 @@ class JSONIOHandlerImpl : public AbstractIOHandlerImpl
void touch(Writable *, Parameter const &) override;
- std::future flush();
+ std::future flush(internal::ParsedFlushParams ¶ms);
private:
#if openPMD_HAVE_MPI
diff --git a/include/openPMD/Iteration.hpp b/include/openPMD/Iteration.hpp
index 0892627f2d..ccaf21d1ae 100644
--- a/include/openPMD/Iteration.hpp
+++ b/include/openPMD/Iteration.hpp
@@ -28,6 +28,7 @@
#include "openPMD/backend/Attributable.hpp"
#include "openPMD/backend/Container.hpp"
#include "openPMD/backend/HierarchyVisitor.hpp"
+#include "openPMD/backend/PerIterationData.hpp"
#include "openPMD/backend/scientific_defaults/ScientificDefaults.hpp"
#include
@@ -122,14 +123,16 @@ namespace internal
*/
bool allow_reopening_implicitly = false;
- /**
- * Whether a step is currently active for this iteration.
- * Used for file-based iteration layout, see Series.hpp for
- * group-based layout.
- * Access via stepStatus() method to automatically select the correct
- * one among both flags.
+ /*
+ * This stores data items that are:
+ *
+ * 1. global in group and variable encodings
+ * 2. per-iteration in file encoding
+ *
+ * The struct is stored as part of the Series and as part of each
+ * Iteration. Access must be distinguished by iteration encoding.
*/
- StepStatus m_stepStatus = StepStatus::NoStep;
+ PerIterationData m_perIterationData;
/**
* Cached copy of the key under which this Iteration lives in
@@ -483,8 +486,8 @@ namespace traits
struct GenerationPolicy
{
constexpr static bool is_noop = false;
- template
- void operator()(Iterator &it)
+ template
+ void operator()(Container &, Iterator &it)
{
it->second.get().m_iterationIndex = it->first;
}
diff --git a/include/openPMD/ParticleSpecies.hpp b/include/openPMD/ParticleSpecies.hpp
index 1ec1ff8d9c..7b13a39784 100644
--- a/include/openPMD/ParticleSpecies.hpp
+++ b/include/openPMD/ParticleSpecies.hpp
@@ -74,8 +74,8 @@ namespace traits
struct GenerationPolicy
{
constexpr static bool is_noop = false;
- template
- void operator()(T &it)
+ template
+ void operator()(Container &, T &it)
{
it->second.particlePatches.linkHierarchy(it->second.writable());
}
diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp
index 3def700f71..e6419702fc 100644
--- a/include/openPMD/RecordComponent.hpp
+++ b/include/openPMD/RecordComponent.hpp
@@ -72,38 +72,41 @@ namespace internal
RecordComponentData &operator=(RecordComponentData const &) = delete;
RecordComponentData &operator=(RecordComponentData &&) = delete;
- /**
- * Chunk reading/writing requests on the contained dataset.
- */
- std::queue m_chunks;
+ [[nodiscard]] auto constantValue() const -> auto const &
+ {
+ return m_dataset_meta->m_constantValue;
+ }
+ auto constantValue() -> auto &
+ {
+ return m_dataset_meta->m_constantValue;
+ }
+ [[nodiscard]] auto isEmpty() const -> auto const &
+ {
+ return m_dataset_meta->m_isEmpty;
+ }
+ auto isEmpty() -> auto &
+ {
+ return m_dataset_meta->m_isEmpty;
+ }
+ [[nodiscard]] auto hasBeenExtended() const -> auto const &
+ {
+ return m_dataset_meta->m_hasBeenExtended;
+ }
+ auto hasBeenExtended() -> auto &
+ {
+ return m_dataset_meta->m_hasBeenExtended;
+ }
- void push_chunk(IOTask &&task);
- /**
- * Stores the value for constant record components.
- * Ignored otherwise.
- */
- Attribute m_constantValue{-1};
- /**
- * True if this component is an empty dataset, i.e. its extent is zero
- * in at least one dimension.
- * Treated by the openPMD-api as a special case of constant record
- * components.
- */
- bool m_isEmpty = false;
- /**
- * User has extended the dataset, but the EXTEND task must yet be
- * flushed to the backend
- */
- bool m_hasBeenExtended = false;
-
- void reset() override
+ [[nodiscard]] auto chunks() const -> auto const &
{
- BaseRecordComponentData::reset();
- m_chunks = std::queue();
- m_constantValue = -1;
- m_isEmpty = false;
- m_hasBeenExtended = false;
+ return m_dataset_meta->m_chunks;
}
+ auto chunks() -> auto &
+ {
+ return m_dataset_meta->m_chunks;
+ }
+
+ void push_chunk(IOTask &&task);
};
template
class BaseRecordData;
@@ -134,6 +137,9 @@ class RecordComponent
friend T &internal::makeOwning(T &self, Series);
friend class internal::ScientificDefaults;
friend class Attributable;
+ template
+ friend class ConvertibleContainer;
+ friend class CustomHierarchy;
public:
enum class Allocation
diff --git a/include/openPMD/RecordComponent.tpp b/include/openPMD/RecordComponent.tpp
index b796ab1a93..0e8c157751 100644
--- a/include/openPMD/RecordComponent.tpp
+++ b/include/openPMD/RecordComponent.tpp
@@ -90,12 +90,13 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer)
{
size *= ext;
}
+
/*
* Flushing the skeleton does not create datasets,
* so we might need to do it now.
*/
auto &rc = get();
- if (!rc.m_dataset.has_value())
+ if (!rc.dataset().has_value())
{
throw error::WrongAPIUsage(
"[RecordComponent] Must specify dataset type and extent before "
@@ -121,7 +122,7 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer)
// restriction
// TODO: Add some form of collective ::commitDefinitions() call to
// RecordComponents to be called by users before the Span API
- if (!written())
+ if (!writable().parent || !writable().parent->written)
{
/*
* The openPMD backend might not yet know about this dataset.
@@ -129,8 +130,11 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer)
* actual data yet.
*/
seriesFlush_impl* flush_entire_series = */ false>(
- {FlushLevel::SkeletonOnly});
- Parameter dCreate(rc.m_dataset.value());
+ {FlushLevel::SkeletonOnly}, /*flush_io_handler=*/false);
+ }
+ if (!this->written())
+ {
+ Parameter dCreate(rc.dataset().value());
dCreate.name = Attributable::get().m_writable.ownKeyWithinParent;
IOHandler()->enqueue(IOTask(this, dCreate));
diff --git a/include/openPMD/Series.hpp b/include/openPMD/Series.hpp
index 93dfe333b4..0b8f480097 100644
--- a/include/openPMD/Series.hpp
+++ b/include/openPMD/Series.hpp
@@ -33,6 +33,7 @@
#include "openPMD/backend/Container.hpp"
#include "openPMD/backend/HierarchyVisitor.hpp"
#include "openPMD/backend/ParsePreference.hpp"
+#include "openPMD/backend/PerIterationData.hpp"
#include "openPMD/config.hpp"
#include "openPMD/snapshots/Snapshots.hpp"
#include "openPMD/version.hpp"
@@ -205,14 +206,18 @@ namespace internal
* Detected IO format (backend).
*/
Format m_format;
- /**
- * Whether a step is currently active for this iteration.
- * Used for group-based iteration layout, see SeriesData.hpp for
- * iteration-based layout.
- * Access via stepStatus() method to automatically select the correct
- * one among both flags.
+
+ /*
+ * This stores data items that are:
+ *
+ * 1. global in group and variable encodings
+ * 2. per-iteration in file encoding
+ *
+ * The struct is stored as part of the Series and as part of each
+ * Iteration. Access must be distinguished by iteration encoding.
*/
- StepStatus m_stepStatus = StepStatus::NoStep;
+ PerIterationData m_perIterationData;
+
/**
* True if a user opts into lazy parsing.
*/
@@ -261,7 +266,6 @@ namespace internal
struct RankTableData
{
- Attributable m_attributable;
std::variant<
NoSourceSpecified,
SourceSpecifiedViaJSON,
@@ -447,6 +451,11 @@ class Series : public Attributable
* basePath.
*/
std::string meshesPath() const;
+ /**
+ * @return String representing the path to mesh records, relative(!) to
+ * basePath.
+ */
+ std::optional meshesPathOptional() const;
/** Set the path to mesh
* records, relative(!) to basePath.
@@ -502,6 +511,11 @@ class Series : public Attributable
* basePath.
*/
std::string particlesPath() const;
+ /**
+ * @return String representing the path to particle species, relative(!) to
+ * basePath.
+ */
+ std::optional particlesPathOptional() const;
/** Set the path to groups for each particle
* species, relative(!) to basePath.
@@ -900,9 +914,7 @@ OPENPMD_private
iterations_iterator end,
internal::FlushParams const &flushParams,
bool flushIOHandler = true);
- void flushMeshesPath();
- void flushParticlesPath();
- void flushRankTable();
+ void flushRankTable(FlushLevel, Attributable &attributable);
/* Parameter `read_only_this_single_iteration` used for reopening an
* Iteration after closing it.
*/
@@ -985,8 +997,10 @@ OPENPMD_private
* least one step was written.
*
* @param doFlush If true, flush the IO handler.
+ * @param l This operation must only run at flush level write_datasets,
+ * Noop otherwise.
*/
- void flushStep(bool doFlush);
+ void flushStep(bool doFlush, FlushLevel l);
/*
* setIterationEncoding() should only be called by users of our public API,
@@ -997,6 +1011,9 @@ OPENPMD_private
Series &setIterationEncoding_internal(
IterationEncoding iterationEncoding, internal::default_or_explicit);
+ Series &setParticlesPath_internal(std::string const &particlesPath);
+ Series &setMeshesPath_internal(std::string const &meshesPath);
+
/*
* Returns the current content of the /data/snapshot attribute.
* (We could also add this to the public API some time)
diff --git a/include/openPMD/auxiliary/Defer.hpp b/include/openPMD/auxiliary/Defer.hpp
index c6bc4e0533..8792a14ee0 100644
--- a/include/openPMD/auxiliary/Defer.hpp
+++ b/include/openPMD/auxiliary/Defer.hpp
@@ -21,7 +21,8 @@ struct defer_type
std::move(functor)();
}
- explicit defer_type() = default;
+ explicit defer_type() : do_run_this(false)
+ {}
struct forwarding_tag
{};
@@ -32,7 +33,8 @@ struct defer_type
{}
template
- defer_type(defer_type &&other) : functor{std::move(other.functor)}
+ defer_type(defer_type &&other)
+ : functor{std::move(other.functor)}, do_run_this(other.do_run_this)
{
other.do_run_this = false;
}
@@ -41,6 +43,7 @@ struct defer_type
auto operator=(defer_type &&other)
{
functor = std::move(other.functor);
+ do_run_this = other.do_run_this;
other.do_run_this = false;
}
diff --git a/include/openPMD/auxiliary/MonadicOperations.hpp b/include/openPMD/auxiliary/MonadicOperations.hpp
new file mode 100644
index 0000000000..c96f682bc8
--- /dev/null
+++ b/include/openPMD/auxiliary/MonadicOperations.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include
+#include
+
+namespace openPMD::auxiliary
+{
+template
+auto optional_and_then(Optional &&arg, F &&then)
+ -> std::invoke_result_t())>
+{
+ if (!arg.has_value())
+ {
+ return std::nullopt;
+ }
+ return std::forward(then)(*std::forward(arg));
+}
+} // namespace openPMD::auxiliary
diff --git a/include/openPMD/auxiliary/TypeTraits.hpp b/include/openPMD/auxiliary/TypeTraits.hpp
index 8d617aad3f..b395c199e1 100644
--- a/include/openPMD/auxiliary/TypeTraits.hpp
+++ b/include/openPMD/auxiliary/TypeTraits.hpp
@@ -163,6 +163,9 @@ using ScalarType_t = typename detail::ScalarType::type;
template
using VectorType_t = std::vector>;
+template
+using dependent_const = std::conditional_t;
+
/** Emulate in the C++ concept ContiguousContainer
*
* Users can implement this trait for a type to signal it can be used as
diff --git a/include/openPMD/backend/Attributable.hpp b/include/openPMD/backend/Attributable.hpp
index f05cc8d15b..84d03b833b 100644
--- a/include/openPMD/backend/Attributable.hpp
+++ b/include/openPMD/backend/Attributable.hpp
@@ -60,6 +60,7 @@ namespace internal
struct HomogenizeExtents;
struct ConfigAttribute;
class ScientificDefaults;
+ class AttributableData;
class SharedAttributableData
{
@@ -112,6 +113,8 @@ namespace internal
using SharedData_t = std::shared_ptr;
using A_MAP = SharedData_t::element_type::A_MAP;
+ using parent_t = std::shared_ptr;
+ friend class openPMD::CustomHierarchy;
public:
AttributableData();
@@ -120,12 +123,24 @@ namespace internal
AttributableData(AttributableData &&) = delete;
virtual ~AttributableData() = default;
+ inline auto asSharedPtrOfAttributable()
+ -> std::shared_ptr &
+ {
+ return *this;
+ }
+
+ [[nodiscard]] inline auto asSharedPtrOfAttributable() const
+ -> std::shared_ptr const &
+ {
+ return *this;
+ }
+
AttributableData &operator=(AttributableData const &) = delete;
AttributableData &operator=(AttributableData &&) = delete;
// Make copies explicit, only to be used under the conditions described
// above
- void cloneFrom(AttributableData const &other);
+ void cloneFrom(parent_t const &other);
template
T asInternalCopyOf()
@@ -249,6 +264,7 @@ class Attributable
friend struct internal::HomogenizeExtents;
friend struct internal::ConfigAttribute;
friend class internal::ScientificDefaults;
+ friend class CustomHierarchy;
protected:
// tag for internal constructor
@@ -294,6 +310,15 @@ class Attributable
*/
Attribute getAttribute(std::string const &key) const;
+ /** Retrieve value of Attribute stored with provided key.
+ *
+ * @throw no_such_attribute_error If no Attribute is currently stored with
+ * the provided key.
+ * @param key Key (i.e. name) of the Attribute to retrieve value for.
+ * @return If found, the stored Attribute in Variant form.
+ */
+ std::optional getAttributeOptional(std::string const &key) const;
+
/** Remove Attribute of provided value both logically and physically.
*
* @param key Key (i.e. name) of the Attribute to remove.
@@ -363,6 +388,9 @@ class Attributable
*/
void iterationFlush(std::string backendConfig = "{}");
+ void customHierarchyFlush(
+ internal::FlushParams const &, bool managed_as_custom_object);
+
/** String serialization to describe an Attributable
*
* This object contains the Series data path as well as the openPMD object
@@ -457,6 +485,25 @@ class Attributable
[[nodiscard]] OpenpmdStandard openPMDStandard() const;
+ // TODO: Add parse? parameter, dont parse, parse this object, parse
+ // recursively
+ // Alternatively, parse upon deferred initialization?
+ // CustomHierarchy is ephemeral, and i dont even know what that word
+ // means. but i want to say that things are created on the spot. if we go to
+ // the same place in the openPMD file using different paths, the objects
+ // will be different, except for the backend data stored in
+ // sharedattributabledata and writable classes. even when later turning a
+ // customhierarchy object into a recordcomponent, this will not be same
+ // recordcomponent object as in the usual openpmd hierarchy.
+ // All data that needs to be shared within instances, is inside
+ // SharedAttributableData (including Writable).
+ // If objects are *created* by customhierarchies, the pointer
+ // writable.attributable will point to that object as the first instance. so
+ // i guess we will still need to store the objects somewhere and cannot keep
+ // them fully ephemeral. This job is done by
+ // GroupMetaData::m_children_managed_as_custom_hierarchy.
+ auto customHierarchies() -> CustomHierarchy;
+
// clang-format off
OPENPMD_protected
// clang-format on
@@ -478,7 +525,7 @@ OPENPMD_protected
/** @} */
template
- void seriesFlush_impl(internal::FlushParams const &);
+ void seriesFlush_impl(internal::FlushParams const &, bool flush_io_handler);
void flushAttributes(internal::FlushParams const &);
@@ -582,6 +629,8 @@ OPENPMD_protected
return (*m_attri)->m_writable;
}
+ void preferCurrentBackpointer() const;
+
inline void setData(std::shared_ptr attri)
{
m_attri = std::move(attri);
@@ -606,6 +655,34 @@ OPENPMD_protected
{
return writable().dirtyRecursive;
}
+ void determineUnsetDirty(FlushLevel fl)
+ {
+ switch (fl)
+ {
+ case FlushLevel::UserFlush:
+ setDirty(false);
+ break;
+ // FlushLevel::InternalFlush is only used for directly calling the IO
+ // handler and should not bother with middle-end state manipulations
+ case FlushLevel::InternalFlush:
+ // Used for parsing
+ if (IOHandler()->m_seriesStatus == internal::SeriesStatus::Parsing)
+ {
+ throw error::Internal(
+ "Parsing procedures should directly unset dirty.");
+ }
+ else
+ {
+ throw error::Internal(
+ "Internal flushes should not unset dirty flags.");
+ }
+ break;
+ case FlushLevel::SkeletonOnly:
+ case FlushLevel::CreateOrOpenFiles:
+ // noop
+ break;
+ }
+ }
void setDirty(bool dirty_in)
{
auto &w = writable();
@@ -730,6 +807,6 @@ Attributable::readVectorFloatingpoint(std::string const &key) const
std::is_floating_point::value,
"Type of attribute must be floating point");
- return getAttribute(key).get >();
+ return getAttribute(key).get>();
}
} // namespace openPMD
diff --git a/include/openPMD/backend/BaseRecord.hpp b/include/openPMD/backend/BaseRecord.hpp
index 295b12e909..b8940fcf29 100644
--- a/include/openPMD/backend/BaseRecord.hpp
+++ b/include/openPMD/backend/BaseRecord.hpp
@@ -409,10 +409,12 @@ template
auto BaseRecord::emplace(Args &&...args) -> std::pair
{
detail::verifyNonscalar(this);
- auto res = this->container().emplace(std::forward(args)...);
+ auto res = this->syncInsertResult(
+ this->container_front().emplace(std::forward(args)...));
if (res.first->first == RecordComponent::SCALAR)
{
- this->container().erase(res.first);
+ this->container_back(/* verify = */ true).erase(res.first->first);
+ this->container_front().erase(res.first);
throw error::WrongAPIUsage(detail::NO_SCALAR_INSERT);
}
return {makeIterator(std::move(res.first)), res.second};
diff --git a/include/openPMD/backend/BaseRecordComponent.hpp b/include/openPMD/backend/BaseRecordComponent.hpp
index af23e08468..baef8454f0 100644
--- a/include/openPMD/backend/BaseRecordComponent.hpp
+++ b/include/openPMD/backend/BaseRecordComponent.hpp
@@ -24,6 +24,7 @@
#include "openPMD/Dataset.hpp"
#include "openPMD/Error.hpp"
#include "openPMD/backend/Attributable.hpp"
+#include "openPMD/backend/Writable.hpp"
#include
@@ -39,38 +40,58 @@ namespace internal
class BaseRecordComponentData : virtual public AttributableData
{
public:
- /**
- * The type and extent of the dataset defined by this component.
- */
- std::optional m_dataset;
- /**
- * True if this is defined as a constant record component as specified
- * in the openPMD standard.
- * If yes, then no heavy-weight dataset is created and the dataset is
- * instead defined via light-weight attributes.
- */
- bool m_isConstant = false;
- /**
- * Tracks if there was any write access to the record component.
- * Necessary in BaseRecord to track if the scalar component has been
- * used and is used by BaseRecord to determine the return value of
- * the BaseRecord::scalar() method.
- */
- bool m_datasetDefined = false;
-
BaseRecordComponentData(BaseRecordComponentData const &) = delete;
BaseRecordComponentData(BaseRecordComponentData &&) = delete;
BaseRecordComponentData &
operator=(BaseRecordComponentData const &) = delete;
BaseRecordComponentData &operator=(BaseRecordComponentData &&) = delete;
+ internal::object_type::DatasetMetaData *m_dataset_meta = nullptr;
+
+ void setDatasetDefined()
+ {
+ m_dataset_meta = (**this).m_writable.objectType.initDataset();
+ }
+
BaseRecordComponentData() = default;
+ [[nodiscard]] inline auto dataset() const -> auto const &
+ {
+ return m_dataset_meta->m_dataset;
+ }
+ inline auto dataset() -> auto &
+ {
+ return m_dataset_meta->m_dataset;
+ }
+
+ [[nodiscard]] inline auto isConstant() const -> auto const &
+ {
+ return m_dataset_meta->m_isConstant;
+ }
+ inline auto isConstant() -> auto &
+ {
+ return m_dataset_meta->m_isConstant;
+ }
+
+ [[nodiscard]] inline auto datasetDefined() const -> bool
+ {
+ return (**this).m_writable.objectType.isDataset();
+ }
+
virtual void reset()
{
- m_dataset = std::nullopt;
- m_isConstant = false;
- m_datasetDefined = false;
+ (**this).m_writable.objectType.initGroup();
+ m_dataset_meta = nullptr;
+ }
+
+ template
+ void cloneFrom(Arg &&arg)
+ {
+ AttributableData::cloneFrom(std::forward(arg));
+ if (datasetDefined())
+ {
+ setDatasetDefined();
+ }
}
};
} // namespace internal
@@ -172,11 +193,14 @@ class BaseRecordComponent : virtual public Attributable
inline Data_t const &get() const
{
+ // cannot call this in the const overload
+ // setDatasetDefined(*m_baseRecordComponentData);
return *m_baseRecordComponentData;
}
inline Data_t &get()
{
+ setDatasetDefined(*m_baseRecordComponentData);
return *m_baseRecordComponentData;
}
diff --git a/include/openPMD/backend/Container.hpp b/include/openPMD/backend/Container.hpp
index 839f6452c9..3a0643538d 100644
--- a/include/openPMD/backend/Container.hpp
+++ b/include/openPMD/backend/Container.hpp
@@ -25,6 +25,7 @@
#include "openPMD/auxiliary/TypeTraits.hpp"
#include "openPMD/backend/Attributable.hpp"
#include "openPMD/backend/HierarchyVisitor.hpp"
+#include "openPMD/backend/Writable.hpp"
#include
#include