diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 94edc99565..f2d7c42218 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -303,6 +303,7 @@ jobs:
find ../../../build/samples/ \
! -path '*thetaMode*' \
! -path '*/many_iterations/*' \
+ ! -path '*/custom_hierarchy/*' \
! -name 'profiling.json' \
! -name '*config.json' \
! -path '*/file_state_lifetime/*' \
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 42ac7e8723..cb542d5e64 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
@@ -429,7 +430,10 @@ set(CORE_SOURCE
src/backend/Attribute.cpp
src/backend/BaseRecord.cpp
src/backend/BaseRecordComponent.cpp
+ src/backend/ChildrenCache.cpp
+ src/backend/ChildView.cpp
src/backend/Container.cpp
+ src/backend/HierarchyVisitor.cpp
src/backend/MeshRecordComponent.cpp
src/backend/PatchRecord.cpp
src/backend/PatchRecordComponent.cpp
@@ -869,9 +873,11 @@ 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
)
elseif(${test_name} STREQUAL "Auxiliary")
list(APPEND ${out_list}
+ test/Files_Auxiliary/children_cache.cpp
test/Files_Auxiliary/drain_set.cpp
)
endif()
diff --git a/include/openPMD/CustomHierarchy.hpp b/include/openPMD/CustomHierarchy.hpp
new file mode 100644
index 0000000000..ccb852c2b0
--- /dev/null
+++ b/include/openPMD/CustomHierarchy.hpp
@@ -0,0 +1,257 @@
+/* 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/auxiliary/Export.hpp"
+#include "openPMD/auxiliary/TypeTraits.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
+{
+ struct CustomHierarchyData : public AttributableData
+ {
+ bool has_been_read = false;
+ };
+} // namespace internal
+
+class CustomDataset : public RecordComponent
+{
+ template
+ friend class Container;
+ template
+ friend class ConvertibleContainer;
+ friend class CustomHierarchy;
+ friend class Attributable;
+
+private:
+ CustomDataset() = default;
+};
+
+/*
+ * This is its own class, so the return value of asContainerOf() is also
+ * convsertible again.
+ */
+template
+class ConvertibleContainer : public Container
+{
+ template
+ friend class ConvertibleContainer;
+ friend class CustomHierarchy;
+
+protected:
+ using Container_t = Container;
+
+ using Container_t::Container_t;
+
+private:
+ explicit ConvertibleContainer() = default;
+ ConvertibleContainer(Attributable::NoInit)
+ : Container_t(Attributable::NoInit{})
+ {}
+
+public:
+ [[nodiscard]] auto isDataset() const -> bool;
+ auto asDataset() -> CustomDataset;
+
+ auto datasets() -> ConvertibleContainer;
+ auto subgroups() -> ConvertibleContainer;
+};
+
+namespace traits
+{
+ template <>
+ struct ElementAccessPolicy
+ {
+ static void call(CustomHierarchy &);
+ static void call(CustomHierarchy const &);
+ };
+
+ namespace detail
+ {
+ template
+ void emplace_object_as_customly_managed(Container &, Iterator &it)
+ {
+ auto &writable = it->second.writable();
+
+ // If this frontend instance is the first one for its backend
+ // object, the reference graph (Writable::attributable -> frontend
+ // data -> SharedAttributableData) is rooted at it. The typed cache
+ // now owns that instance, so only record that the object was
+ // created through the custom hierarchy.
+ auto backpointer = writable.attributable;
+ auto emplaced_pointer = it->second.m_attri.get();
+ if (backpointer == emplaced_pointer)
+ {
+ (**it->second.m_attri).managed_as_custom_hierarchy = true;
+ }
+ }
+ } // namespace detail
+
+ template <>
+ struct GenerationPolicy
+ {
+ constexpr static bool is_noop = false;
+ template
+ void operator()(Container &cont, Iterator &it)
+ {
+ detail::emplace_object_as_customly_managed(
+ cont, it);
+ }
+ };
+
+ template <>
+ struct GenerationPolicy
+ {
+ constexpr static bool is_noop = false;
+ template
+ void operator()(Container &cont, Iterator &it)
+ {
+ detail::emplace_object_as_customly_managed(
+ cont, it);
+ }
+ };
+} // namespace traits
+
+class CustomHierarchy : public ConvertibleContainer
+{
+ friend class Iteration;
+ friend class Container;
+ friend class Attributable;
+ friend struct traits::ElementAccessPolicy;
+ template
+ friend class Container;
+ template
+ friend class ConvertibleContainer;
+ template
+ friend struct internal::ChildMaterializer;
+
+private:
+ using Parent_t = ConvertibleContainer;
+ using Container_t = typename Parent_t::Container_t;
+ using Data_t = internal::CustomHierarchyData;
+
+ std::shared_ptr m_customHierarchyData;
+
+ inline void setData(std::shared_ptr customHierarchyData)
+ {
+ m_customHierarchyData = std::move(customHierarchyData);
+ ConvertibleContainer::setData(m_customHierarchyData);
+ }
+
+ inline auto get() -> Data_t &
+ {
+ return *m_customHierarchyData;
+ }
+
+ [[nodiscard]] inline auto get() const -> Data_t const &
+ {
+ return *m_customHierarchyData;
+ }
+
+protected:
+ CustomHierarchy();
+ CustomHierarchy(NoInit);
+ CustomHierarchy(std::shared_ptr other);
+ CustomHierarchy(Attributable const &other);
+
+ void flush_internal(
+ internal::FlushParams const &, std::vector currentPath);
+ void flush(std::string const &path, internal::FlushParams const &);
+
+ /**
+ * @brief Link with parent.
+ *
+ * @param w The Writable representing the parent.
+ */
+ void linkHierarchy(Writable &w) override;
+
+public:
+ 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);
+
+protected:
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
+};
+namespace internal
+{
+ template <>
+ struct ChildMaterializer
+ {
+ static auto materialize(
+ std::string const &,
+ std::shared_ptr const &child)
+ -> std::optional
+ {
+ return CustomHierarchy(child);
+ }
+ };
+
+ template <>
+ struct ChildMaterializer
+ {
+ static auto materialize(
+ std::string const &,
+ std::shared_ptr const &child)
+ -> std::optional
+ {
+ CustomHierarchy custom{child};
+ if (!custom.isDataset())
+ {
+ return std::nullopt;
+ }
+ return custom.asDataset();
+ }
+ };
+} // namespace internal
+} // namespace openPMD
diff --git a/include/openPMD/Iteration.hpp b/include/openPMD/Iteration.hpp
index f1a4fd436b..e959e6db66 100644
--- a/include/openPMD/Iteration.hpp
+++ b/include/openPMD/Iteration.hpp
@@ -153,14 +153,14 @@ namespace internal
class Meshes : public Container
{
-public:
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
+protected:
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
};
class Particles : public Container
{
-public:
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
+protected:
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
};
/** @brief Logical compilation of data from one snapshot (e.g. a single
@@ -301,8 +301,6 @@ class Iteration
[[deprecated("This attribute is no longer set by the openPMD-api.")]] bool
closedByWriter() const;
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
-
Meshes meshes{};
Particles particles{};
@@ -478,6 +476,8 @@ class Iteration
protected:
void scientificDefaults_impl(
internal::WriteOrRead, OpenpmdStandard) override;
+
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
}; // Iteration
namespace traits
@@ -486,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;
}
@@ -544,7 +544,7 @@ class IndexedIteration : public Iteration
class Iterations : public Container
{
-public:
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
+protected:
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
};
} // namespace openPMD
diff --git a/include/openPMD/Mesh.hpp b/include/openPMD/Mesh.hpp
index bf8ad794fb..3cbcb3e53b 100644
--- a/include/openPMD/Mesh.hpp
+++ b/include/openPMD/Mesh.hpp
@@ -327,8 +327,6 @@ class Mesh : public BaseRecord
typename = std::enable_if_t::value>>
Mesh &setTimeOffset(T timeOffset);
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
-
private:
Mesh();
@@ -340,6 +338,8 @@ class Mesh : public BaseRecord
protected:
void scientificDefaults_impl(
internal::WriteOrRead, OpenpmdStandard) override;
+
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
}; // Mesh
template
diff --git a/include/openPMD/ParticlePatches.hpp b/include/openPMD/ParticlePatches.hpp
index ffe539d5f1..32c7f29b4b 100644
--- a/include/openPMD/ParticlePatches.hpp
+++ b/include/openPMD/ParticlePatches.hpp
@@ -38,11 +38,12 @@ class ParticlePatches : public Container
size_t numPatches() const;
~ParticlePatches() override = default;
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
-
private:
ParticlePatches() = default;
void read();
+
+protected:
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
}; // ParticlePatches
} // namespace openPMD
diff --git a/include/openPMD/ParticleSpecies.hpp b/include/openPMD/ParticleSpecies.hpp
index df6024db31..f4b604b8f1 100644
--- a/include/openPMD/ParticleSpecies.hpp
+++ b/include/openPMD/ParticleSpecies.hpp
@@ -48,24 +48,24 @@ class ParticleSpecies
public:
ParticlePatches particlePatches;
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
-
private:
ParticleSpecies();
void read();
- void flush(std::string const &, internal::FlushParams const &) override;
+ void flush(std::string const &, internal::FlushParams const &);
- using Data_t = Container::ContainerData;
+ using Data_t = internal::AttributableData;
inline std::shared_ptr getShared()
{
- return m_containerData;
+ return m_attri;
}
protected:
void scientificDefaults_impl(
internal::WriteOrRead, OpenpmdStandard) override;
+
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
};
namespace traits
@@ -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/Record.hpp b/include/openPMD/Record.hpp
index af397925dc..f9bf79f974 100644
--- a/include/openPMD/Record.hpp
+++ b/include/openPMD/Record.hpp
@@ -50,8 +50,6 @@ class Record : public BaseRecord
template
Record &setTimeOffset(T);
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
-
private:
Record();
@@ -63,6 +61,8 @@ class Record : public BaseRecord
protected:
void scientificDefaults_impl(
internal::WriteOrRead, OpenpmdStandard) override;
+
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
}; // Record
template
diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp
index e8052d2f3e..b274d6f94b 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 dataset_meta()->m_constantValue;
+ }
+ auto constantValue() -> auto &
+ {
+ return dataset_meta()->m_constantValue;
+ }
+ [[nodiscard]] auto isEmpty() const -> auto const &
+ {
+ return dataset_meta()->m_isEmpty;
+ }
+ auto isEmpty() -> auto &
+ {
+ return dataset_meta()->m_isEmpty;
+ }
+ [[nodiscard]] auto hasBeenExtended() const -> auto const &
+ {
+ return dataset_meta()->m_hasBeenExtended;
+ }
+ auto hasBeenExtended() -> auto &
+ {
+ return 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 &
+ {
+ return dataset_meta()->m_chunks;
+ }
+ auto chunks() -> auto &
{
- BaseRecordComponentData::reset();
- m_chunks = std::queue();
- m_constantValue = -1;
- m_isEmpty = false;
- m_hasBeenExtended = false;
+ return 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_type);
friend class internal::ScientificDefaults;
friend class Attributable;
+ template
+ friend class ConvertibleContainer;
+ friend class CustomHierarchy;
public:
enum class Allocation
@@ -488,14 +494,14 @@ class RecordComponent
auto visit(Args &&...args) -> decltype(Visitor::template call(
std::declval(), std::forward(args)...));
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
-
static constexpr char const *const SCALAR = "\vScalar";
protected:
void flush(std::string const &, internal::FlushParams const &);
void read();
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
+
private:
/**
* Internal method to be called by all methods that create an empty dataset.
diff --git a/include/openPMD/RecordComponent.tpp b/include/openPMD/RecordComponent.tpp
index 951a520cfb..0e8c157751 100644
--- a/include/openPMD/RecordComponent.tpp
+++ b/include/openPMD/RecordComponent.tpp
@@ -96,7 +96,7 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer)
* 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 "
@@ -134,7 +134,7 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer)
}
if (!this->written())
{
- Parameter dCreate(rc.m_dataset.value());
+ 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 33ee1e9a88..eb1a9e8550 100644
--- a/include/openPMD/Series.hpp
+++ b/include/openPMD/Series.hpp
@@ -27,6 +27,7 @@
#include "openPMD/Iteration.hpp"
#include "openPMD/IterationEncoding.hpp"
#include "openPMD/Streaming.hpp"
+#include "openPMD/auxiliary/Export.hpp"
#include "openPMD/auxiliary/TypeTraits.hpp"
#include "openPMD/auxiliary/Variant.hpp"
#include "openPMD/backend/Attributable.hpp"
@@ -453,6 +454,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.
@@ -508,6 +514,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.
@@ -786,8 +797,6 @@ class Series : public Attributable
[[nodiscard]] bool closed() const;
- void visitHierarchy(HierarchyVisitor &v, bool recursive) override;
-
/**
* This overrides Attributable::iterationFlush() which will fail on Series.
*/
@@ -1010,6 +1019,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)
@@ -1038,6 +1050,9 @@ OPENPMD_private
[[nodiscard]] bool randomAccessSteps() const;
std::vector availableDatasets();
+
+protected:
+ void visitHierarchyImpl(HierarchyVisitor &v, bool recursive) override;
}; // Series
namespace debug
diff --git a/include/openPMD/auxiliary/Export.hpp b/include/openPMD/auxiliary/Export.hpp
index 394397a092..1ab25a077c 100644
--- a/include/openPMD/auxiliary/Export.hpp
+++ b/include/openPMD/auxiliary/Export.hpp
@@ -22,7 +22,15 @@
#ifndef OPENPMDAPI_EXPORT
#ifdef _MSC_VER
+// openPMD_EXPORTS is defined by CMake while building the library. Consumers
+// (including the tests) must not see dllexport on these symbols: MSVC would
+// otherwise expect the annotated non-inline members to be defined in the
+// consuming translation unit.
+#ifdef openPMD_EXPORTS
#define OPENPMDAPI_EXPORT __declspec(dllexport)
+#else
+#define OPENPMDAPI_EXPORT
+#endif
#elif defined(__NVCC__)
#define OPENPMDAPI_EXPORT
#else
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/auxiliary/Variant.hpp b/include/openPMD/auxiliary/Variant.hpp
index 988076b94c..50bc23a0e2 100644
--- a/include/openPMD/auxiliary/Variant.hpp
+++ b/include/openPMD/auxiliary/Variant.hpp
@@ -97,6 +97,8 @@ namespace auxiliary
*/
[[nodiscard]] size_t index() const;
+ void print() const;
+
T_DTYPES dtype;
private:
diff --git a/include/openPMD/backend/Attributable.hpp b/include/openPMD/backend/Attributable.hpp
index b69e4e7a46..4032d640c9 100644
--- a/include/openPMD/backend/Attributable.hpp
+++ b/include/openPMD/backend/Attributable.hpp
@@ -23,6 +23,7 @@
#include "openPMD/Error.hpp"
#include "openPMD/IO/AbstractIOHandler.hpp"
#include "openPMD/ThrowError.hpp"
+#include "openPMD/auxiliary/Export.hpp"
#include "openPMD/auxiliary/OutOfRangeMsg.hpp"
#include "openPMD/backend/Attribute.hpp"
#include "openPMD/backend/HierarchyVisitor.hpp"
@@ -47,6 +48,13 @@ namespace traits
{
template
struct GenerationPolicy;
+ template
+ struct ElementAccessPolicy;
+ namespace detail
+ {
+ template
+ void emplace_object_as_customly_managed(Container &cont, Iterator &it);
+ }
} // namespace traits
class AbstractFilePosition;
class Attributable;
@@ -57,9 +65,12 @@ namespace internal
{
class IterationData;
class SeriesData;
+ template
+ class ChildView;
struct HomogenizeExtents;
struct ConfigAttribute;
class ScientificDefaults;
+ class AttributableData;
class SharedAttributableData
{
@@ -87,6 +98,18 @@ namespace internal
* The attributes defined by this Attributable.
*/
A_MAP m_attributes;
+
+ /**
+ * True if the frontend instance owning Writable::attributable was
+ * created through a custom-hierarchy view and has not yet been
+ * replaced by a regular typed view.
+ *
+ * This cannot be inferred from the typed caches: a cache can contain
+ * either a custom-created handle or an alias materialized from the
+ * regular hierarchy. The flag distinguishes those cases for
+ * custom-hierarchy flush/visit and for preferCurrentBackpointer().
+ */
+ bool managed_as_custom_hierarchy = false;
};
/*
@@ -112,6 +135,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 +145,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()
@@ -244,11 +281,19 @@ class Attributable
template
friend T &internal::makeOwning(T &self, Series_type);
friend class StatefulSnapshotsContainer;
+ template
+ friend class internal::ChildView;
friend class internal::AttributableData;
friend class Snapshots;
friend struct internal::HomogenizeExtents;
friend struct internal::ConfigAttribute;
friend class internal::ScientificDefaults;
+ friend class CustomHierarchy;
+ template
+ friend struct traits::ElementAccessPolicy;
+ template
+ friend void traits::detail::emplace_object_as_customly_managed(
+ Container &cont, Iterator &it);
protected:
// tag for internal constructor
@@ -294,6 +339,14 @@ class Attributable
*/
Attribute getAttribute(std::string const &key) const;
+ /** Retrieve value of Attribute stored with provided key.
+ *
+ * @param key Key (i.e. name) of the Attribute to retrieve value for.
+ * @return If found, the stored Attribute in Variant form. Otherwise, an
+ * empty std::optional.
+ */
+ 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.
@@ -418,7 +471,7 @@ class Attributable
* @param visitor Operations to run for each object.
* @param recursive Extend the operation recursively to children.
*/
- virtual void visitHierarchy(HierarchyVisitor &visitor, bool recursive);
+ void visitHierarchy(HierarchyVisitor &visitor, bool recursive);
/**
* Visitor pattern for the openPMD object hierarchy in postfix traversal,
@@ -464,6 +517,18 @@ class Attributable
*/
[[nodiscard]] uintptr_t memoryID() const;
+ /** Return a generic view of this object's hierarchy.
+ *
+ * The returned view is lazy: children are read from the backend and
+ * represented by frontend handles only when they are accessed.
+ *
+ * Children created or erased through the returned view affect the regular
+ * openPMD hierarchy. Handles created through this view are kept alive by
+ * the containing backend group, independently of the lifetime of the
+ * returned handle.
+ */
+ auto customHierarchies() -> CustomHierarchy;
+
// clang-format off
OPENPMD_protected
// clang-format on
@@ -489,6 +554,9 @@ OPENPMD_protected
void flushAttributes(internal::FlushParams const &);
+ void customHierarchyFlush(
+ internal::FlushParams const &, bool managed_as_custom_object);
+
enum ReadMode
{
/**
@@ -589,6 +657,8 @@ OPENPMD_protected
return (*m_attri)->m_writable;
}
+ void preferCurrentBackpointer() const;
+
inline void setData(std::shared_ptr attri)
{
m_attri = std::move(attri);
@@ -699,6 +769,9 @@ OPENPMD_protected
*/
void setWritten(bool val, EnqueueAsynchronously);
+protected:
+ virtual void visitHierarchyImpl(HierarchyVisitor &visitor, bool recursive);
+
private:
/**
* @brief Link with parent.
@@ -765,6 +838,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 3acdb04b86..fd5c6ebbb0 100644
--- a/include/openPMD/backend/BaseRecord.hpp
+++ b/include/openPMD/backend/BaseRecord.hpp
@@ -49,7 +49,7 @@ namespace internal
*/
typename T_RecordComponentData = typename T_elem::Data_t>
class BaseRecordData final
- : public ContainerData
+ : virtual public AttributableData
, public T_RecordComponentData
{
using T_RecordComponent = T_elem;
@@ -248,22 +248,18 @@ class BaseRecord
using pointer = typename Container::pointer;
using const_pointer = typename Container::const_pointer;
- using iterator = internal::ScalarIterator<
- T_Self,
- Data_t,
- typename T_Container::InternalContainer::iterator>;
+ using iterator = internal::
+ ScalarIterator;
using const_iterator = internal::ScalarIterator<
T_Self const,
Data_t const,
- typename T_Container::InternalContainer::const_iterator>;
- using reverse_iterator = internal::ScalarIterator<
- T_Self,
- Data_t,
- typename T_Container::InternalContainer::reverse_iterator>;
+ typename T_Container::const_iterator>;
+ using reverse_iterator = internal::
+ ScalarIterator;
using const_reverse_iterator = internal::ScalarIterator<
T_Self const,
Data_t const,
- typename T_Container::InternalContainer::const_reverse_iterator>;
+ typename T_Container::const_reverse_iterator>;
private:
template
@@ -322,7 +318,6 @@ class BaseRecord
template
void insert(InputIt first, InputIt last);
void insert(std::initializer_list ilist);
- void swap(BaseRecord &other) noexcept;
bool contains(key_type const &key) const;
template
auto emplace(Args &&...args) -> std::pair;
@@ -352,7 +347,7 @@ class BaseRecord
void setDatasetDefined(BaseRecordComponent::Data_t &data) override
{
- if (!T_Container::empty())
+ if (this->writable().objectType.isGroup() && !T_Container::empty())
{
throw error::WrongAPIUsage(
"A scalar component can not be contained at the same time as "
@@ -368,7 +363,7 @@ class BaseRecord
bool scalar() const;
private:
- void flush(std::string const &, internal::FlushParams const &) final;
+ void flush(std::string const &, internal::FlushParams const &);
virtual void
flush_impl(std::string const &, internal::FlushParams const &) = 0;
@@ -382,7 +377,7 @@ class BaseRecord
namespace detail
{
constexpr char const *const NO_SCALAR_INSERT =
- "[BaseRecord] emplace()/insert()/swap() API invalid for scalar "
+ "[BaseRecord] emplace()/insert() API invalid for scalar "
"records. Use the Record directly as a RecordComponent.";
template
@@ -404,15 +399,28 @@ namespace detail
constexpr bool IsBaseRecord_v = IsBaseRecord::value;
} // namespace detail
+template
+template
+auto BaseRecord::insert(InputIt first, InputIt last) -> void
+{
+ detail::verifyNonscalar(this);
+ T_Container::insert(first, last);
+ /*
+ * We skip checking for the SCALAR key as that would change the runtime of
+ * this call from O(last-first) to O(container().size()).
+ */
+}
+
template
template
auto BaseRecord::emplace(Args &&...args) -> std::pair
{
detail::verifyNonscalar(this);
- auto res = this->container().emplace(std::forward(args)...);
+ auto res = T_Container::emplace(std::forward(args)...);
if (res.first->first == RecordComponent::SCALAR)
{
- this->container().erase(res.first);
+ auto const key = res.first->first;
+ this->erase_unchecked(key);
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..9b34d12fc6 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,76 @@ 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_ptr = nullptr;
+
+ void setDatasetDefined()
+ {
+ m_dataset_meta_ptr = (**this).m_writable.objectType.initDataset();
+ }
+
BaseRecordComponentData() = default;
+ auto dataset_meta() const
+ -> internal::object_type::DatasetMetaData const *
+ {
+ if (!m_dataset_meta_ptr)
+ {
+ throw error::WrongAPIUsage(
+ "Requesting dataset properties on an object that has no "
+ "dataset defined.");
+ }
+ return m_dataset_meta_ptr;
+ }
+ auto dataset_meta() -> internal::object_type::DatasetMetaData *
+ {
+ return const_cast(
+ static_cast(this)
+ ->dataset_meta());
+ }
+
+ [[nodiscard]] inline auto dataset() const -> auto const &
+ {
+ return dataset_meta()->m_dataset;
+ }
+ inline auto dataset() -> auto &
+ {
+ return dataset_meta()->m_dataset;
+ }
+
+ [[nodiscard]] inline auto isConstant() const -> auto const &
+ {
+ return dataset_meta()->m_isConstant;
+ }
+ inline auto isConstant() -> auto &
+ {
+ return 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_ptr = nullptr;
+ }
+
+ template
+ void cloneFrom(Arg &&arg)
+ {
+ AttributableData::cloneFrom(std::forward(arg));
+ if (datasetDefined())
+ {
+ setDatasetDefined();
+ }
}
};
} // namespace internal
@@ -172,11 +211,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/ChildView.hpp b/include/openPMD/backend/ChildView.hpp
new file mode 100644
index 0000000000..d32be63309
--- /dev/null
+++ b/include/openPMD/backend/ChildView.hpp
@@ -0,0 +1,344 @@
+/* Copyright 2026 The openPMD Community
+ *
+ * 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/Access.hpp"
+#include "openPMD/backend/Attributable.hpp"
+#include "openPMD/backend/ChildrenCache.hpp"
+#include "openPMD/backend/Writable.hpp"
+
+#include
+#include
+#include
+#include
+#include