From 0bed2b0c89104c97820e136b58de3e25ddd038db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Fri, 17 Sep 2021 16:35:23 +0200 Subject: [PATCH 1/7] Make EraseStaleEntries independent template class Users can now choose whether to copy the container or not Necessary if working on internal (non-copiable) classes --- include/openPMD/backend/Container.hpp | 35 +++++++++++++++++++-------- src/Iteration.cpp | 4 +-- src/Mesh.cpp | 2 +- src/ParticleSpecies.cpp | 6 ++--- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/include/openPMD/backend/Container.hpp b/include/openPMD/backend/Container.hpp index 156e55e292..e37e6f169e 100644 --- a/include/openPMD/backend/Container.hpp +++ b/include/openPMD/backend/Container.hpp @@ -59,6 +59,7 @@ namespace traits namespace internal { class SeriesData; +template< typename > class EraseStaleEntries; } namespace detail @@ -114,6 +115,7 @@ class Container : public LegacyAttributable friend class internal::SeriesData; friend class SeriesInterface; friend class Series; + template< typename > friend class internal::EraseStaleEntries; protected: using InternalContainer = T_container; @@ -325,7 +327,10 @@ class Container : public LegacyAttributable } std::shared_ptr< InternalContainer > m_container; +}; +namespace internal +{ /** * This class wraps a Container and forwards operator[]() and at() to it. * It remembers the keys used for accessing. Upon going out of scope, all @@ -333,9 +338,15 @@ class Container : public LegacyAttributable * Note that the container is stored by non-owning reference, thus * requiring that the original Container stay in scope while using this * class. + * Container_t can be instantiated either by a reference or value type. */ + template< typename Container_t > class EraseStaleEntries { + using BareContainer_t = + typename std::remove_reference< Container_t >::type; + using key_type = typename BareContainer_t::key_type; + using mapped_type = typename BareContainer_t::mapped_type; std::set< key_type > m_accessedKeys; /* * Note: Putting a copy here leads to weird bugs due to destructors @@ -344,14 +355,24 @@ class Container : public LegacyAttributable * Container class template * (https://github.com/openPMD/openPMD-api/pull/886) */ - Container & m_originalContainer; + Container_t m_originalContainer; public: - explicit EraseStaleEntries( Container & container_in ) + explicit EraseStaleEntries( + Container_t & container_in ) : m_originalContainer( container_in ) { } + explicit EraseStaleEntries( + BareContainer_t && container_in ) + : m_originalContainer( std::move( container_in ) ) + { + } + + EraseStaleEntries( EraseStaleEntries && ) = default; + EraseStaleEntries & operator=( EraseStaleEntries && ) = default; + template< typename K > mapped_type & operator[]( K && k ) { @@ -369,7 +390,7 @@ class Container : public LegacyAttributable ~EraseStaleEntries() { auto & map = *m_originalContainer.m_container; - using iterator_t = typename InternalContainer::const_iterator; + using iterator_t = typename BareContainer_t::const_iterator; std::vector< iterator_t > deleteMe; deleteMe.reserve( map.size() - m_accessedKeys.size() ); for( iterator_t it = map.begin(); it != map.end(); ++it ) @@ -386,11 +407,5 @@ class Container : public LegacyAttributable } } }; - - EraseStaleEntries eraseStaleEntries() - { - return EraseStaleEntries( *this ); - } -}; - +} // internal } // openPMD diff --git a/src/Iteration.cpp b/src/Iteration.cpp index ff0c1b1016..ada079bce9 100644 --- a/src/Iteration.cpp +++ b/src/Iteration.cpp @@ -447,7 +447,7 @@ void Iteration::read_impl( std::string const & groupPath ) meshes.readAttributes( ReadMode::FullyReread ); - auto map = meshes.eraseStaleEntries(); + internal::EraseStaleEntries< decltype( meshes ) > map{ meshes }; /* obtain all non-scalar meshes */ IOHandler()->enqueue(IOTask(&meshes, pList)); @@ -517,7 +517,7 @@ void Iteration::read_impl( std::string const & groupPath ) IOHandler()->enqueue(IOTask(&particles, pList)); IOHandler()->flush(); - auto map = particles.eraseStaleEntries(); + internal::EraseStaleEntries< decltype( particles ) > map{ particles }; for( auto const& species_name : *pList.paths ) { ParticleSpecies& p = map[species_name]; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index e9551c42e6..f8cbfe4102 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -274,7 +274,7 @@ Mesh::flush_impl(std::string const& name) void Mesh::read() { - auto map = eraseStaleEntries(); + internal::EraseStaleEntries< Mesh & > map{ *this }; using DT = Datatype; Parameter< Operation::READ_ATT > aRead; diff --git a/src/ParticleSpecies.cpp b/src/ParticleSpecies.cpp index 55e50df5fe..81a6f5996b 100644 --- a/src/ParticleSpecies.cpp +++ b/src/ParticleSpecies.cpp @@ -42,7 +42,7 @@ ParticleSpecies::read() IOHandler()->enqueue(IOTask(this, pList)); IOHandler()->flush(); - auto map = eraseStaleEntries(); + internal::EraseStaleEntries< ParticleSpecies & > map{ *this }; Parameter< Operation::OPEN_PATH > pOpen; Parameter< Operation::LIST_ATTS > aList; @@ -70,7 +70,7 @@ ParticleSpecies::read() auto shape = std::find(att_begin, att_end, "shape"); if( value != att_end && shape != att_end ) { - auto scalarMap = r.eraseStaleEntries(); + internal::EraseStaleEntries< Record & > scalarMap( r ); RecordComponent& rc = scalarMap[RecordComponent::SCALAR]; rc.parent() = r.parent(); IOHandler()->enqueue(IOTask(&rc, pOpen)); @@ -101,7 +101,7 @@ ParticleSpecies::read() dOpen.name = record_name; IOHandler()->enqueue(IOTask(&r, dOpen)); IOHandler()->flush(); - auto scalarMap = r.eraseStaleEntries(); + internal::EraseStaleEntries< Record & > scalarMap( r ); RecordComponent& rc = scalarMap[RecordComponent::SCALAR]; rc.parent() = r.parent(); IOHandler()->enqueue(IOTask(&rc, dOpen)); From 4a0b1729a5c37830b16ae22c5ab50a22d54a3297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 16 Sep 2021 16:37:31 +0200 Subject: [PATCH 2/7] Container: Split into data and interface class Same goes for deriving classes, except those that have no own data. --- include/openPMD/RecordComponent.hpp | 88 ++++++++--- include/openPMD/RecordComponent.tpp | 25 +-- include/openPMD/backend/Attributable.hpp | 13 +- include/openPMD/backend/BaseRecord.hpp | 109 +++++++++---- .../openPMD/backend/BaseRecordComponent.hpp | 76 +++++++++- include/openPMD/backend/Container.hpp | 143 ++++++++++++------ .../openPMD/backend/PatchRecordComponent.hpp | 76 ++++++++-- include/openPMD/backend/Writable.hpp | 2 + src/Iteration.cpp | 2 +- src/Mesh.cpp | 2 +- src/ParticleSpecies.cpp | 4 +- src/Record.cpp | 2 +- src/RecordComponent.cpp | 108 +++++++------ src/backend/Attributable.cpp | 2 +- src/backend/BaseRecordComponent.cpp | 30 ++-- src/backend/PatchRecordComponent.cpp | 46 ++++-- 16 files changed, 522 insertions(+), 206 deletions(-) diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp index 7ccebcd3e3..c67920778e 100644 --- a/include/openPMD/RecordComponent.hpp +++ b/include/openPMD/RecordComponent.hpp @@ -78,6 +78,40 @@ struct IsContiguousContainer< std::array< T_Value, N > > template< typename T > class DynamicMemoryView; +class RecordComponent; + +namespace internal +{ + class RecordComponentData : public BaseRecordComponentData + { + friend class openPMD::RecordComponent; + + RecordComponentData(); + + RecordComponentData( RecordComponentData const & ) = delete; + RecordComponentData( RecordComponentData && ) = delete; + + RecordComponentData & operator=( RecordComponentData const & ) = delete; + RecordComponentData & operator=( RecordComponentData && ) = delete; + + std::queue< IOTask > m_chunks; + Attribute m_constantValue{ -1 }; + /** + * The same std::string that the parent class would pass as parameter to + * RecordComponent::flush(). + * This is stored only upon RecordComponent::flush() if + * AbstractIOHandler::flushLevel is set to FlushLevel::SkeletonOnly + * (for use by the Span-based overload of RecordComponent::storeChunk()). + * @todo Merge functionality with ownKeyInParent? + */ + std::string m_name; + bool m_isEmpty = false; + // User has extended the dataset, but the EXTEND task must yet be + // flushed to the backend + bool m_hasBeenExtended = false; + }; +} + class RecordComponent : public BaseRecordComponent { template< @@ -90,10 +124,14 @@ class RecordComponent : public BaseRecordComponent friend class ParticleSpecies; template< typename T_elem > friend class BaseRecord; + template< typename T_elem > + friend class BaseRecordInterface; friend class Record; friend class Mesh; template< typename > friend class DynamicMemoryView; + friend class internal::RecordComponentData; + friend class MeshRecordComponent; public: enum class Allocation @@ -248,21 +286,6 @@ class RecordComponent : public BaseRecordComponent static constexpr char const * const SCALAR = "\vScalar"; - virtual ~RecordComponent() = default; - -OPENPMD_protected: - RecordComponent(); - - void readBase(); - - std::shared_ptr< std::queue< IOTask > > m_chunks; - std::shared_ptr< Attribute > m_constantValue; - std::shared_ptr< bool > m_isEmpty = std::make_shared< bool >( false ); - // User has extended the dataset, but the EXTEND task must yet be flushed - // to the backend - std::shared_ptr< bool > m_hasBeenExtended = - std::make_shared< bool >( false ); - private: void flush(std::string const&); virtual void read(); @@ -285,19 +308,34 @@ class RecordComponent : public BaseRecordComponent */ bool dirtyRecursive() const; -protected: + std::shared_ptr< internal::RecordComponentData > m_recordComponentData{ + new internal::RecordComponentData() }; - /** - * The same std::string that the parent class would pass as parameter to - * RecordComponent::flush(). - * This is stored only upon RecordComponent::flush() if - * AbstractIOHandler::flushLevel is set to FlushLevel::SkeletonOnly - * (for use by the Span-based overload of RecordComponent::storeChunk()). - * @todo Merge functionality with ownKeyInParent? - */ - std::shared_ptr< std::string > m_name = std::make_shared< std::string >(); + RecordComponent(); + +OPENPMD_protected: + RecordComponent( std::shared_ptr< internal::RecordComponentData > ); + + inline internal::RecordComponentData const & get() const + { + return *m_recordComponentData; + } + inline internal::RecordComponentData & get() + { + return const_cast< internal::RecordComponentData & >( + static_cast< RecordComponent const * >( this )->get() ); + } + + inline void setData( std::shared_ptr< internal::RecordComponentData > data ) + { + m_recordComponentData = std::move( data ); + BaseRecordComponent::setData( m_recordComponentData ); + } + + void readBase(); }; // RecordComponent + } // namespace openPMD #include "RecordComponent.tpp" diff --git a/include/openPMD/RecordComponent.tpp b/include/openPMD/RecordComponent.tpp index 92ec2beb38..89a7100b56 100644 --- a/include/openPMD/RecordComponent.tpp +++ b/include/openPMD/RecordComponent.tpp @@ -33,8 +33,10 @@ RecordComponent::makeConstant(T value) if( written() ) throw std::runtime_error("A recordComponent can not (yet) be made constant after it has been written."); - *m_constantValue = Attribute(value); - *m_isConstant = true; + auto & rc = get(); + + rc.m_constantValue = Attribute(value); + rc.m_isConstant = true; return *this; } @@ -133,13 +135,14 @@ inline void RecordComponent::loadChunk( if( !data ) throw std::runtime_error("Unallocated pointer passed during chunk loading."); + auto & rc = get(); if( constant() ) { uint64_t numPoints = 1u; for( auto const& dimensionSize : extent ) numPoints *= dimensionSize; - T value = m_constantValue->get< T >(); + T value = rc.m_constantValue.get< T >(); T* raw_ptr = data.get(); std::fill(raw_ptr, raw_ptr + numPoints, value); @@ -150,7 +153,7 @@ inline void RecordComponent::loadChunk( dRead.extent = extent; dRead.dtype = getDatatype(); dRead.data = std::static_pointer_cast< void >(data); - m_chunks->push(IOTask(this, dRead)); + rc.m_chunks.push(IOTask(this, dRead)); } } @@ -201,7 +204,8 @@ RecordComponent::storeChunk(std::shared_ptr data, Offset o, Extent e) dWrite.dtype = dtype; /* std::static_pointer_cast correctly reference-counts the pointer */ dWrite.data = std::static_pointer_cast< void const >(data); - m_chunks->push(IOTask(this, dWrite)); + auto & rc = get(); + rc.m_chunks.push(IOTask(this, dWrite)); } template< typename T_ContiguousContainer > @@ -289,14 +293,15 @@ RecordComponent::storeChunk( Offset o, Extent e, F && createBuffer ) */ if( !written() ) { + auto & rc = get(); Parameter< Operation::CREATE_DATASET > dCreate; - dCreate.name = *m_name; + dCreate.name = rc.m_name; dCreate.extent = getExtent(); dCreate.dtype = getDatatype(); - dCreate.chunkSize = m_dataset->chunkSize; - dCreate.compression = m_dataset->compression; - dCreate.transform = m_dataset->transform; - dCreate.options = m_dataset->options; + dCreate.chunkSize = rc.m_dataset.chunkSize; + dCreate.compression = rc.m_dataset.compression; + dCreate.transform = rc.m_dataset.transform; + dCreate.options = rc.m_dataset.options; IOHandler()->enqueue(IOTask(this, dCreate)); } Parameter< Operation::GET_BUFFER_VIEW > getBufferView; diff --git a/include/openPMD/backend/Attributable.hpp b/include/openPMD/backend/Attributable.hpp index 157d732345..7d37406fef 100644 --- a/include/openPMD/backend/Attributable.hpp +++ b/include/openPMD/backend/Attributable.hpp @@ -105,6 +105,7 @@ attr_value_check( std::string const key, std::string const value ) "' must not be empty!" ); } +template< typename > class BaseRecordData; } // namespace internal /** @brief Layer to manage storage of attributes associated with file objects. @@ -119,6 +120,10 @@ class AttributableInterface friend Writable* getWritable(AttributableInterface*); template< typename T_elem > friend class BaseRecord; + template< typename T_elem > + friend class BaseRecordInterface; + template< typename > + friend class internal::BaseRecordData; template< typename T, typename T_key, @@ -351,6 +356,12 @@ class AttributableInterface return m_attri->m_writable; } + inline + void setData( internal::AttributableData * attri ) + { + m_attri = attri; + } + inline internal::AttributableData & get() { @@ -407,7 +418,7 @@ class LegacyAttributable : public AttributableInterface public: LegacyAttributable() : AttributableInterface{ nullptr } { - AttributableInterface::m_attri = m_attributableData.get(); + AttributableInterface::setData( m_attributableData.get() ); } }; diff --git a/include/openPMD/backend/BaseRecord.hpp b/include/openPMD/backend/BaseRecord.hpp index d37f861908..065e7158c9 100644 --- a/include/openPMD/backend/BaseRecord.hpp +++ b/include/openPMD/backend/BaseRecord.hpp @@ -32,11 +32,65 @@ namespace openPMD { +template< typename > class BaseRecord; + +namespace internal +{ + template< typename T_elem > + class BaseRecordData : public ContainerData< T_elem > + { + template< typename > friend class openPMD::BaseRecord; + + protected: + bool m_containsScalar = false; + + public: + BaseRecordData(); + + BaseRecordData( BaseRecordData const & ) = delete; + BaseRecordData( BaseRecordData && ) = delete; + + BaseRecordData & operator=( BaseRecordData const & ) = delete; + BaseRecordData & operator=( BaseRecordData && ) = delete; + }; +} + template< typename T_elem > class BaseRecord : public Container< T_elem > { friend class Iteration; friend class ParticleSpecies; + friend class PatchRecord; + friend class Record; + + friend class Mesh; + friend class ParticleSpecies; + + std::shared_ptr< internal::BaseRecordData< T_elem > > m_baseRecordData{ + new internal::BaseRecordData< T_elem >() }; + + inline internal::BaseRecordData< T_elem > & get() + { + return const_cast< internal::BaseRecordData< T_elem > & >( + static_cast< BaseRecord const * >( this )->get() ); + } + + inline internal::BaseRecordData< T_elem > const & get() const + { + return *m_baseRecordData; + } + + BaseRecord(); + +protected: + + BaseRecord( std::shared_ptr< internal::BaseRecordData< T_elem > > ); + + inline void setData( internal::BaseRecordData< T_elem > * data ) + { + m_baseRecordData = std::move( data ); + Container< T_elem >::setData( m_baseRecordData ); + } public: using key_type = typename Container< T_elem >::key_type; @@ -52,8 +106,6 @@ class BaseRecord : public Container< T_elem > using iterator = typename Container< T_elem >::iterator; using const_iterator = typename Container< T_elem >::const_iterator; - BaseRecord(BaseRecord const& b); - BaseRecord& operator=(BaseRecord const& b); virtual ~BaseRecord() = default; mapped_type& operator[](key_type const& key) override; @@ -88,11 +140,9 @@ class BaseRecord : public Container< T_elem > bool scalar() const; protected: - BaseRecord(); + BaseRecord( internal::BaseRecordData< T_elem > * ); void readBase(); - std::shared_ptr< bool > m_containsScalar; - private: void flush(std::string const&) final; virtual void flush_impl(std::string const&) = 0; @@ -111,32 +161,37 @@ class BaseRecord : public Container< T_elem > }; // BaseRecord -template< typename T_elem > -BaseRecord< T_elem >::BaseRecord(BaseRecord const& b) - : Container< T_elem >(b), - m_containsScalar{b.m_containsScalar} -{ } +// implementation + +namespace internal +{ + template< typename T_elem > + BaseRecordData< T_elem >::BaseRecordData() + { + AttributableInterface impl{ this }; + impl.setAttribute( + "unitDimension", + std::array< double, 7 >{ { 0., 0., 0., 0., 0., 0., 0. } } ); + } +} // namespace internal template< typename T_elem > -BaseRecord< T_elem >& BaseRecord< T_elem >::operator=(openPMD::BaseRecord const& b) { - Container< T_elem >::operator=( b ); - m_containsScalar = b.m_containsScalar; - return *this; +BaseRecord< T_elem >::BaseRecord() : Container< T_elem >{ nullptr } +{ + Container< T_elem >::setData( m_baseRecordData ); } template< typename T_elem > -BaseRecord< T_elem >::BaseRecord() - : Container< T_elem >(), - m_containsScalar{std::make_shared< bool >(false)} +BaseRecord< T_elem >::BaseRecord( + std::shared_ptr< internal::BaseRecordData< T_elem > > data ) + : Container< T_elem >{ data } + , m_baseRecordData{ std::move( data ) } { - this->setAttribute("unitDimension", - std::array< double, 7 >{{0., 0., 0., 0., 0., 0., 0.}}); } - template< typename T_elem > -inline typename BaseRecord< T_elem >::mapped_type& -BaseRecord< T_elem >::operator[](key_type const& key) +inline typename BaseRecord< T_elem >::mapped_type & +BaseRecord< T_elem >::operator[]( key_type const & key ) { auto it = this->find(key); if( it != this->end() ) @@ -151,7 +206,7 @@ BaseRecord< T_elem >::operator[](key_type const& key) mapped_type& ret = Container< T_elem >::operator[](key); if( keyScalar ) { - *m_containsScalar = true; + get().m_containsScalar = true; ret.parent() = this->parent(); } return ret; @@ -175,7 +230,7 @@ BaseRecord< T_elem >::operator[](key_type&& key) mapped_type& ret = Container< T_elem >::operator[](std::move(key)); if( keyScalar ) { - *m_containsScalar = true; + get().m_containsScalar = true; ret.parent() = this->parent(); } return ret; @@ -207,7 +262,7 @@ BaseRecord< T_elem >::erase(key_type const& key) { this->written() = false; this->writable().abstractFilePosition.reset(); - *this->m_containsScalar = false; + this->get().m_containsScalar = false; } return res; } @@ -237,7 +292,7 @@ BaseRecord< T_elem >::erase(iterator res) { this->written() = false; this->writable().abstractFilePosition.reset(); - *this->m_containsScalar = false; + this->get().m_containsScalar = false; } return ret; } @@ -253,7 +308,7 @@ template< typename T_elem > inline bool BaseRecord< T_elem >::scalar() const { - return *m_containsScalar; + return get().m_containsScalar; } template< typename T_elem > diff --git a/include/openPMD/backend/BaseRecordComponent.hpp b/include/openPMD/backend/BaseRecordComponent.hpp index 0d50557744..693159dd2d 100644 --- a/include/openPMD/backend/BaseRecordComponent.hpp +++ b/include/openPMD/backend/BaseRecordComponent.hpp @@ -22,6 +22,7 @@ #include "openPMD/backend/Attributable.hpp" #include "openPMD/Dataset.hpp" +#include "openPMD/Error.hpp" // expose private and protected members for invasive testing #ifndef OPENPMD_protected @@ -31,7 +32,48 @@ namespace openPMD { -class BaseRecordComponent : public LegacyAttributable +class BaseRecordComponent; +class Iteration; +class Mesh; +class ParticleSpecies; +class PatchRecordComponent; +class PatchRecordComponentInterface; +class Record; +class RecordComponent; +class RecordComponentInterface; + +namespace internal +{ + class BaseRecordComponentData : public AttributableData + { + friend class openPMD::BaseRecordComponent; + friend class openPMD::Iteration; + friend class openPMD::Mesh; + friend class openPMD::ParticleSpecies; + friend class openPMD::PatchRecordComponent; + friend class openPMD::PatchRecordComponentInterface; + friend class openPMD::Record; + friend class openPMD::RecordComponent; + friend class openPMD::RecordComponentInterface; + + + Dataset m_dataset{ Datatype::UNDEFINED, {} }; + bool m_isConstant = false; + + BaseRecordComponentData( BaseRecordComponentData const & ) = delete; + BaseRecordComponentData( BaseRecordComponentData && ) = delete; + + BaseRecordComponentData & operator=( + BaseRecordComponentData const & ) = delete; + BaseRecordComponentData & operator=( + BaseRecordComponentData && ) = delete; + + protected: + BaseRecordComponentData() = default; + }; +} + +class BaseRecordComponent : public AttributableInterface { template< typename T, @@ -42,8 +84,6 @@ class BaseRecordComponent : public LegacyAttributable class Container; public: - ~BaseRecordComponent() = default; - double unitSI() const; BaseRecordComponent& resetDatatype(Datatype); @@ -79,10 +119,34 @@ class BaseRecordComponent : public LegacyAttributable ChunkTable availableChunks(); - OPENPMD_protected : BaseRecordComponent(); +protected: + + std::shared_ptr< internal::BaseRecordComponentData > + m_baseRecordComponentData{ + new internal::BaseRecordComponentData() }; + + inline internal::BaseRecordComponentData const & get() const + { + return *m_baseRecordComponentData; + } + + inline internal::BaseRecordComponentData & get() + { + return const_cast< internal::BaseRecordComponentData & >( + static_cast< BaseRecordComponent const * >( this )->get() ); + } + + inline void setData( + std::shared_ptr< internal::BaseRecordComponentData > data ) + { + m_baseRecordComponentData = std::move( data ); + AttributableInterface::setData( m_baseRecordComponentData.get() ); + } + + BaseRecordComponent( std::shared_ptr< internal::BaseRecordComponentData > ); - std::shared_ptr< Dataset > m_dataset; - std::shared_ptr< bool > m_isConstant; +private: + BaseRecordComponent(); }; // BaseRecordComponent namespace detail diff --git a/include/openPMD/backend/Container.hpp b/include/openPMD/backend/Container.hpp index e37e6f169e..646573ca47 100644 --- a/include/openPMD/backend/Container.hpp +++ b/include/openPMD/backend/Container.hpp @@ -20,6 +20,7 @@ */ #pragma once +#include "openPMD/Error.hpp" #include "openPMD/backend/Attributable.hpp" #include @@ -58,8 +59,28 @@ namespace traits namespace internal { -class SeriesData; -template< typename > class EraseStaleEntries; + class SeriesData; + template< typename > class EraseStaleEntries; + + template< + typename T, + typename T_key = std::string, + typename T_container = std::map< T_key, T > > + class ContainerData : public AttributableData + { + public: + using InternalContainer = T_container; + + InternalContainer m_container; + + ContainerData() = default; + + ContainerData( ContainerData const & ) = delete; + ContainerData( ContainerData && ) = delete; + + ContainerData & operator=( ContainerData const & ) = delete; + ContainerData & operator=( ContainerData && ) = delete; + }; } namespace detail @@ -100,11 +121,10 @@ std::vector< std::string > keyAsString< std::string >( * @tparam T_container Type of container used for internal storage (must supply the same type traits and interface as std::map) */ template< - typename T, - typename T_key = std::string, - typename T_container = std::map< T_key, T > -> -class Container : public LegacyAttributable + typename T, + typename T_key = std::string, + typename T_container = std::map< T_key, T > > +class Container : public AttributableInterface { static_assert( std::is_base_of< AttributableInterface, T >::value, @@ -112,14 +132,36 @@ class Container : public LegacyAttributable friend class Iteration; friend class ParticleSpecies; + friend class ParticlePatches; friend class internal::SeriesData; friend class SeriesInterface; friend class Series; template< typename > friend class internal::EraseStaleEntries; protected: + using ContainerData = internal::ContainerData< T, T_key, T_container >; using InternalContainer = T_container; + std::shared_ptr< ContainerData > m_containerData{ + new ContainerData() }; + + inline void setData( std::shared_ptr< ContainerData > containerData ) + { + m_containerData = std::move( containerData ); + AttributableInterface::setData( m_containerData.get() ); + } + + inline InternalContainer const & container() const + { + return m_containerData->m_container; + } + + inline InternalContainer & container() + { + return const_cast< InternalContainer & >( + static_cast< Container const * >( this )->container() ); + } + public: using key_type = typename InternalContainer::key_type; using mapped_type = typename InternalContainer::mapped_type; @@ -134,20 +176,17 @@ class Container : public LegacyAttributable using iterator = typename InternalContainer::iterator; using const_iterator = typename InternalContainer::const_iterator; - Container(Container const&) = default; - virtual ~Container() = default; - - iterator begin() noexcept { return m_container->begin(); } - const_iterator begin() const noexcept { return m_container->begin(); } - const_iterator cbegin() const noexcept { return m_container->cbegin(); } + iterator begin() noexcept { return container().begin(); } + const_iterator begin() const noexcept { return container().begin(); } + const_iterator cbegin() const noexcept { return container().cbegin(); } - iterator end() noexcept { return m_container->end(); } - const_iterator end() const noexcept { return m_container->end(); } - const_iterator cend() const noexcept { return m_container->cend(); } + iterator end() noexcept { return container().end(); } + const_iterator end() const noexcept { return container().end(); } + const_iterator cend() const noexcept { return container().cend(); } - bool empty() const noexcept { return m_container->empty(); } + bool empty() const noexcept { return container().empty(); } - size_type size() const noexcept { return m_container->size(); } + size_type size() const noexcept { return container().size(); } /** Remove all objects from the container and (if written) from disk. * @@ -162,20 +201,20 @@ class Container : public LegacyAttributable clear_unchecked(); } - std::pair< iterator, bool > insert(value_type const& value) { return m_container->insert(value); } + std::pair< iterator, bool > insert(value_type const& value) { return container().insert(value); } template< class P > - std::pair< iterator, bool > insert(P&& value) { return m_container->insert(value); } - iterator insert(const_iterator hint, value_type const& value) { return m_container->insert(hint, value); } + std::pair< iterator, bool > insert(P&& value) { return container().insert(value); } + iterator insert(const_iterator hint, value_type const& value) { return container().insert(hint, value); } template< class P > - iterator insert(const_iterator hint, P&& value) { return m_container->insert(hint, value); } + iterator insert(const_iterator hint, P&& value) { return container().insert(hint, value); } template< class InputIt > - void insert(InputIt first, InputIt last) { m_container->insert(first, last); } - void insert(std::initializer_list< value_type > ilist) { m_container->insert(ilist); } + void insert(InputIt first, InputIt last) { container().insert(first, last); } + void insert(std::initializer_list< value_type > ilist) { container().insert(ilist); } - void swap(Container & other) { m_container->swap(other.m_container); } + void swap(Container & other) { container().swap(other.m_container); } - mapped_type& at(key_type const& key) { return m_container->at(key); } - mapped_type const& at(key_type const& key) const { return m_container->at(key); } + mapped_type& at(key_type const& key) { return container().at(key); } + mapped_type const& at(key_type const& key) const { return container().at(key); } /** Access the value that is mapped to a key equivalent to key, creating it if such key does not exist already. * @@ -185,8 +224,8 @@ class Container : public LegacyAttributable */ virtual mapped_type& operator[](key_type const& key) { - auto it = m_container->find(key); - if( it != m_container->end() ) + auto it = container().find(key); + if( it != container().end() ) return it->second; else { @@ -198,7 +237,7 @@ class Container : public LegacyAttributable T t = T(); t.linkHierarchy(writable()); - auto& ret = m_container->insert({key, std::move(t)}).first->second; + auto& ret = container().insert({key, std::move(t)}).first->second; ret.writable().ownKeyWithinParent = detail::keyAsString( key, writable().ownKeyWithinParent ); traits::GenerationPolicy< T > gen; @@ -214,8 +253,8 @@ class Container : public LegacyAttributable */ virtual mapped_type& operator[](key_type&& key) { - auto it = m_container->find(key); - if( it != m_container->end() ) + auto it = container().find(key); + if( it != container().end() ) return it->second; else { @@ -227,7 +266,7 @@ class Container : public LegacyAttributable T t = T(); t.linkHierarchy(writable()); - auto& ret = m_container->insert({key, std::move(t)}).first->second; + auto& ret = container().insert({key, std::move(t)}).first->second; ret.writable().ownKeyWithinParent = detail::keyAsString( std::move( key ), writable().ownKeyWithinParent ); traits::GenerationPolicy< T > gen; @@ -236,22 +275,22 @@ class Container : public LegacyAttributable } } - iterator find(key_type const& key) { return m_container->find(key); } - const_iterator find(key_type const& key) const { return m_container->find(key); } + iterator find(key_type const& key) { return container().find(key); } + const_iterator find(key_type const& key) const { return container().find(key); } /** This returns either 1 if the key is found in the container of 0 if not. * * @param key key value of the element to count * @return since keys are unique in this container, returns 0 or 1 */ - size_type count(key_type const& key) const { return m_container->count(key); } + size_type count(key_type const& key) const { return container().count(key); } /** Checks if there is an element with a key equivalent to an exiting key in the container. * * @param key key value of the element to search for * @return true of key is found, else false */ - bool contains(key_type const& key) const { return m_container->find(key) != m_container->end(); } + bool contains(key_type const& key) const { return container().find(key) != container().end(); } /** Remove a single element from the container and (if written) from disk. * @@ -265,15 +304,15 @@ class Container : public LegacyAttributable if(Access::READ_ONLY == IOHandler()->m_frontendAccess ) throw std::runtime_error("Can not erase from a container in a read-only Series."); - auto res = m_container->find(key); - if( res != m_container->end() && res->second.written() ) + auto res = container().find(key); + if( res != container().end() && res->second.written() ) { Parameter< Operation::DELETE_PATH > pDelete; pDelete.path = "."; IOHandler()->enqueue(IOTask(&res->second, pDelete)); IOHandler()->flush(); } - return m_container->erase(key); + return container().erase(key); } //! @todo why does const_iterator not work compile with pybind11? @@ -282,14 +321,14 @@ class Container : public LegacyAttributable if(Access::READ_ONLY == IOHandler()->m_frontendAccess ) throw std::runtime_error("Can not erase from a container in a read-only Series."); - if( res != m_container->end() && res->second.written() ) + if( res != container().end() && res->second.written() ) { Parameter< Operation::DELETE_PATH > pDelete; pDelete.path = "."; IOHandler()->enqueue(IOTask(&res->second, pDelete)); IOHandler()->flush(); } - return m_container->erase(res); + return container().erase(res); } //! @todo add also: // virtual iterator erase(const_iterator first, const_iterator last) @@ -298,20 +337,22 @@ class Container : public LegacyAttributable auto emplace(Args&&... args) -> decltype(InternalContainer().emplace(std::forward(args)...)) { - return m_container->emplace(std::forward(args)...); + return container().emplace(std::forward(args)...); } OPENPMD_protected: - Container() - : m_container{std::make_shared< InternalContainer >()} - { } + Container( std::shared_ptr< ContainerData > containerData ) + : AttributableInterface{ containerData.get() } + , m_containerData{ std::move( containerData ) } + { + } void clear_unchecked() { if( written() ) throw std::runtime_error("Clearing a written container not (yet) implemented."); - m_container->clear(); + container().clear(); } virtual void flush(std::string const& path) @@ -326,7 +367,11 @@ class Container : public LegacyAttributable flushAttributes(); } - std::shared_ptr< InternalContainer > m_container; +private: + Container() : AttributableInterface{ nullptr } + { + AttributableInterface::setData( m_containerData.get() ); + } }; namespace internal @@ -389,7 +434,7 @@ namespace internal ~EraseStaleEntries() { - auto & map = *m_originalContainer.m_container; + auto & map = m_originalContainer.container(); using iterator_t = typename BareContainer_t::const_iterator; std::vector< iterator_t > deleteMe; deleteMe.reserve( map.size() - m_accessedKeys.size() ); diff --git a/include/openPMD/backend/PatchRecordComponent.hpp b/include/openPMD/backend/PatchRecordComponent.hpp index b0aed3a0d1..5912f3fb2f 100644 --- a/include/openPMD/backend/PatchRecordComponent.hpp +++ b/include/openPMD/backend/PatchRecordComponent.hpp @@ -36,22 +36,42 @@ namespace openPMD { +class PatchRecordComponent; +class PatchRecordComponent; +namespace internal +{ + class PatchRecordComponentData : public BaseRecordComponentData + { + friend class openPMD::PatchRecordComponent; + friend class openPMD::PatchRecordComponent; + + std::queue< IOTask > m_chunks; + + PatchRecordComponentData( PatchRecordComponentData const & ) = delete; + PatchRecordComponentData( PatchRecordComponentData && ) = delete; + + PatchRecordComponentData & operator=( + PatchRecordComponentData const & ) = delete; + PatchRecordComponentData & operator=( + PatchRecordComponentData && ) = delete; + + protected: + PatchRecordComponentData(); + }; +} + /** * @todo add support for constant patch record components */ class PatchRecordComponent : public BaseRecordComponent { - template< - typename T, - typename T_key, - typename T_container - > - friend - class Container; - + template< typename T, typename T_key, typename T_container > + friend class Container; template< typename > friend class BaseRecord; friend class ParticlePatches; friend class PatchRecord; + friend class ParticleSpecies; + friend class internal::PatchRecordComponentData; public: PatchRecordComponent& setUnitSI(double); @@ -63,19 +83,18 @@ class PatchRecordComponent : public BaseRecordComponent template< typename T > std::shared_ptr< T > load(); + template< typename T > void load(std::shared_ptr< T >); + template< typename T > void store(uint64_t idx, T); OPENPMD_private: - PatchRecordComponent(); void flush(std::string const&); void read(); - std::shared_ptr< std::queue< IOTask > > m_chunks; - /** * @brief Check recursively whether this RecordComponent is dirty. * It is dirty if any attribute or dataset is read from or written to @@ -86,6 +105,35 @@ class PatchRecordComponent : public BaseRecordComponent */ bool dirtyRecursive() const; + + std::shared_ptr< internal::PatchRecordComponentData > + m_patchRecordComponentData{ + new internal::PatchRecordComponentData() }; + + PatchRecordComponent(); + +protected: + PatchRecordComponent( + std::shared_ptr< internal::PatchRecordComponentData > ); + + inline internal::PatchRecordComponentData const & get() const + { + return *m_patchRecordComponentData; + } + + inline internal::PatchRecordComponentData & get() + { + return const_cast< internal::PatchRecordComponentData & >( + static_cast< PatchRecordComponent const * >( this ) + ->get() ); + } + + inline void setData( + std::shared_ptr< internal::PatchRecordComponentData > data ) + { + m_patchRecordComponentData = std::move( data ); + BaseRecordComponent::setData( m_patchRecordComponentData ); + } }; // PatchRecordComponent template< typename T > @@ -118,7 +166,8 @@ PatchRecordComponent::load(std::shared_ptr< T > data) dRead.extent = {numPoints}; dRead.dtype = getDatatype(); dRead.data = std::static_pointer_cast< void >(data); - m_chunks->push(IOTask(this, dRead)); + auto & rc = get(); + rc.m_chunks.push(IOTask(this, dRead)); } template< typename T > @@ -147,6 +196,7 @@ PatchRecordComponent::store(uint64_t idx, T data) dWrite.extent = {1}; dWrite.dtype = dtype; dWrite.data = std::make_shared< T >(data); - m_chunks->push(IOTask(this, dWrite)); + auto & rc = get(); + rc.m_chunks.push(IOTask(this, dWrite)); } } // namespace openPMD diff --git a/include/openPMD/backend/Writable.hpp b/include/openPMD/backend/Writable.hpp index 1e7adbf5b6..013b2223c9 100644 --- a/include/openPMD/backend/Writable.hpp +++ b/include/openPMD/backend/Writable.hpp @@ -67,6 +67,8 @@ class Writable final friend class AttributableInterface; template< typename T_elem > friend class BaseRecord; + template< typename T_elem > + friend class BaseRecordInterface; template< typename T, typename T_key, diff --git a/src/Iteration.cpp b/src/Iteration.cpp index ada079bce9..405e680c2f 100644 --- a/src/Iteration.cpp +++ b/src/Iteration.cpp @@ -473,7 +473,7 @@ void Iteration::read_impl( std::string const & groupPath ) mrc.parent() = m.parent(); IOHandler()->enqueue(IOTask(&mrc, pOpen)); IOHandler()->flush(); - *mrc.m_isConstant = true; + mrc.get().m_isConstant = true; } m.read(); } diff --git a/src/Mesh.cpp b/src/Mesh.cpp index f8cbfe4102..73f584e901 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -366,7 +366,7 @@ Mesh::read() MeshRecordComponent& rc = map[ component ]; pOpen.path = component; IOHandler()->enqueue(IOTask(&rc, pOpen)); - *rc.m_isConstant = true; + rc.get().m_isConstant = true; rc.read(); } diff --git a/src/ParticleSpecies.cpp b/src/ParticleSpecies.cpp index 81a6f5996b..191a277a9f 100644 --- a/src/ParticleSpecies.cpp +++ b/src/ParticleSpecies.cpp @@ -75,7 +75,7 @@ ParticleSpecies::read() rc.parent() = r.parent(); IOHandler()->enqueue(IOTask(&rc, pOpen)); IOHandler()->flush(); - *rc.m_isConstant = true; + rc.get().m_isConstant = true; } r.read(); } @@ -83,7 +83,7 @@ ParticleSpecies::read() if( !hasParticlePatches ) { - auto & container = *particlePatches.m_container; + auto & container = particlePatches.container(); container.erase( "numParticles" ); container.erase( "numParticlesOffset" ); } diff --git a/src/Record.cpp b/src/Record.cpp index 989eea975b..abe3013ba1 100644 --- a/src/Record.cpp +++ b/src/Record.cpp @@ -112,7 +112,7 @@ Record::read() RecordComponent& rc = (*this)[component]; pOpen.path = component; IOHandler()->enqueue(IOTask(&rc, pOpen)); - *rc.m_isConstant = true; + rc.get().m_isConstant = true; rc.read(); } diff --git a/src/RecordComponent.cpp b/src/RecordComponent.cpp index a7ee2548ff..db4378a594 100644 --- a/src/RecordComponent.cpp +++ b/src/RecordComponent.cpp @@ -36,18 +36,33 @@ namespace openPMD { -// We need to instantiate this somewhere otherwise there might be linker issues -// despite this thing actually being constepxr -constexpr char const * const RecordComponent::SCALAR; +namespace internal +{ + RecordComponentData::RecordComponentData() + { + RecordComponent impl{ std::shared_ptr< RecordComponentData >{ + this, []( auto const * ) {} } }; + impl.setUnitSI(1); + impl.resetDataset(Dataset(Datatype::CHAR, {1})); + } +} -RecordComponent::RecordComponent() - : m_chunks{std::make_shared< std::queue< IOTask > >()}, - m_constantValue{std::make_shared< Attribute >(-1)} +RecordComponent::RecordComponent() : BaseRecordComponent{ nullptr } { - setUnitSI(1); - resetDataset(Dataset(Datatype::CHAR, {1})); + BaseRecordComponent::setData( m_recordComponentData ); } +RecordComponent::RecordComponent( + std::shared_ptr< internal::RecordComponentData > data ) + : BaseRecordComponent{ data } + , m_recordComponentData{ std::move( data ) } +{ +} + +// We need to instantiate this somewhere otherwise there might be linker issues +// despite this thing actually being constepxr +constexpr char const * const RecordComponent::SCALAR; + RecordComponent& RecordComponent::setUnitSI(double usi) { @@ -58,18 +73,19 @@ RecordComponent::setUnitSI(double usi) RecordComponent & RecordComponent::resetDataset( Dataset d ) { + auto & rc = get(); if( written() ) { if( d.dtype == Datatype::UNDEFINED ) { - d.dtype = m_dataset->dtype; + d.dtype = rc.m_dataset.dtype; } - else if( d.dtype != m_dataset->dtype ) + else if( d.dtype != rc.m_dataset.dtype ) { throw std::runtime_error( "Cannot change the datatype of a dataset." ); } - *m_hasBeenExtended = true; + rc.m_hasBeenExtended = true; } if( d.dtype == Datatype::UNDEFINED ) @@ -85,14 +101,14 @@ RecordComponent::resetDataset( Dataset d ) []( Extent::value_type const & i ) { return i == 0u; } ) ) return makeEmpty( std::move( d ) ); - *m_isEmpty = false; + rc.m_isEmpty = false; if( written() ) { - m_dataset->extend( std::move( d.extent ) ); + rc.m_dataset.extend( std::move( d.extent ) ); } else { - *m_dataset = std::move( d ); + rc.m_dataset = std::move( d ); } dirty() = true; @@ -102,13 +118,13 @@ RecordComponent::resetDataset( Dataset d ) uint8_t RecordComponent::getDimensionality() const { - return m_dataset->rank; + return get().m_dataset.rank; } Extent RecordComponent::getExtent() const { - return m_dataset->extent; + return get().m_dataset.extent; } namespace detail @@ -116,7 +132,8 @@ namespace detail struct MakeEmpty { template< typename T > - static RecordComponent& call( RecordComponent & rc, uint8_t dimensions ) + static RecordComponent& call( + RecordComponent & rc, uint8_t dimensions ) { return rc.makeEmpty< T >( dimensions ); } @@ -139,6 +156,7 @@ RecordComponent::makeEmpty( Datatype dt, uint8_t dimensions ) RecordComponent& RecordComponent::makeEmpty( Dataset d ) { + auto & rc = get(); if( written() ) { if( !constant() ) @@ -150,30 +168,30 @@ RecordComponent::makeEmpty( Dataset d ) } if( d.dtype == Datatype::UNDEFINED ) { - d.dtype = m_dataset->dtype; + d.dtype = rc.m_dataset.dtype; } - else if( d.dtype != m_dataset->dtype ) + else if( d.dtype != rc.m_dataset.dtype ) { throw std::runtime_error( "Cannot change the datatype of a dataset." ); } - m_dataset->extend( std::move( d.extent ) ); - *m_hasBeenExtended = true; + rc.m_dataset.extend( std::move( d.extent ) ); + rc.m_hasBeenExtended = true; } else { - *m_dataset = std::move( d ); + rc.m_dataset = std::move( d ); } - if( m_dataset->extent.size() == 0 ) + if( rc.m_dataset.extent.size() == 0 ) throw std::runtime_error( "Dataset extent must be at least 1D." ); - *m_isEmpty = true; + rc.m_isEmpty = true; dirty() = true; if( !written() ) { switchType< detail::DefaultValue< RecordComponent > >( - m_dataset->dtype, *this ); + rc.m_dataset.dtype, *this ); } return *this; } @@ -181,30 +199,31 @@ RecordComponent::makeEmpty( Dataset d ) bool RecordComponent::empty() const { - return *m_isEmpty; + return get().m_isEmpty; } void RecordComponent::flush(std::string const& name) { + auto & rc = get(); if( IOHandler()->m_flushLevel == FlushLevel::SkeletonOnly ) { - *this->m_name = name; + rc.m_name = name; return; } if(IOHandler()->m_frontendAccess == Access::READ_ONLY ) { - while( !m_chunks->empty() ) + while( !rc.m_chunks.empty() ) { - IOHandler()->enqueue(m_chunks->front()); - m_chunks->pop(); + IOHandler()->enqueue(rc.m_chunks.front()); + rc.m_chunks.pop(); } } else { /* * This catches when a user forgets to use resetDataset. */ - if( m_dataset->dtype == Datatype::UNDEFINED ) + if( rc.m_dataset.dtype == Datatype::UNDEFINED ) { throw error::WrongAPIUsage( "[RecordComponent] Must set specific datatype (Use " @@ -219,8 +238,8 @@ RecordComponent::flush(std::string const& name) IOHandler()->enqueue(IOTask(this, pCreate)); Parameter< Operation::WRITE_ATT > aWrite; aWrite.name = "value"; - aWrite.dtype = m_constantValue->dtype; - aWrite.resource = m_constantValue->getResource(); + aWrite.dtype = rc.m_constantValue.dtype; + aWrite.resource = rc.m_constantValue.getResource(); IOHandler()->enqueue(IOTask(this, aWrite)); aWrite.name = "shape"; Attribute a(getExtent()); @@ -233,15 +252,15 @@ RecordComponent::flush(std::string const& name) dCreate.name = name; dCreate.extent = getExtent(); dCreate.dtype = getDatatype(); - dCreate.chunkSize = m_dataset->chunkSize; - dCreate.compression = m_dataset->compression; - dCreate.transform = m_dataset->transform; - dCreate.options = m_dataset->options; + dCreate.chunkSize = rc.m_dataset.chunkSize; + dCreate.compression = rc.m_dataset.compression; + dCreate.transform = rc.m_dataset.transform; + dCreate.options = rc.m_dataset.options; IOHandler()->enqueue(IOTask(this, dCreate)); } } - if( *m_hasBeenExtended ) + if( rc.m_hasBeenExtended ) { if( constant() ) { @@ -255,16 +274,16 @@ RecordComponent::flush(std::string const& name) else { Parameter< Operation::EXTEND_DATASET > pExtend; - pExtend.extent = m_dataset->extent; + pExtend.extent = rc.m_dataset.extent; IOHandler()->enqueue( IOTask( this, std::move( pExtend ) ) ); - *m_hasBeenExtended = false; + rc.m_hasBeenExtended = false; } } - while( !m_chunks->empty() ) + while( !rc.m_chunks.empty() ) { - IOHandler()->enqueue(m_chunks->front()); - m_chunks->pop(); + IOHandler()->enqueue(rc.m_chunks.front()); + rc.m_chunks.pop(); } flushAttributes(); @@ -281,6 +300,7 @@ void RecordComponent::readBase() { using DT = Datatype; + //auto & rc = get(); Parameter< Operation::READ_ATT > aRead; if( constant() && !empty() ) @@ -396,6 +416,6 @@ RecordComponent::dirtyRecursive() const { return true; } - return !m_chunks->empty(); + return !get().m_chunks.empty(); } } // namespace openPMD diff --git a/src/backend/Attributable.cpp b/src/backend/Attributable.cpp index c20ae0b508..82a6e42d63 100644 --- a/src/backend/Attributable.cpp +++ b/src/backend/Attributable.cpp @@ -39,8 +39,8 @@ AttributableData::AttributableData() : m_writable{ this } } AttributableInterface::AttributableInterface( internal::AttributableData * attri ) - : m_attri{ attri } { + setData( attri ); } Attribute diff --git a/src/backend/BaseRecordComponent.cpp b/src/backend/BaseRecordComponent.cpp index e099e21b06..de9174a329 100644 --- a/src/backend/BaseRecordComponent.cpp +++ b/src/backend/BaseRecordComponent.cpp @@ -35,34 +35,30 @@ BaseRecordComponent::resetDatatype(Datatype d) if( written() ) throw std::runtime_error("A Records Datatype can not (yet) be changed after it has been written."); - m_dataset->dtype = d; + get().m_dataset.dtype = d; return *this; } -BaseRecordComponent::BaseRecordComponent() - : m_dataset{std::make_shared< Dataset >(Dataset(Datatype::UNDEFINED, {}))}, - m_isConstant{std::make_shared< bool >(false)} -{ } - Datatype BaseRecordComponent::getDatatype() const { - return m_dataset->dtype; + return get().m_dataset.dtype; } bool BaseRecordComponent::constant() const { - return *m_isConstant; + return get().m_isConstant; } ChunkTable BaseRecordComponent::availableChunks() { - if( m_isConstant && *m_isConstant ) + auto & rc = get(); + if( rc.m_isConstant ) { - Offset offset( m_dataset->extent.size(), 0 ); - return ChunkTable{ { std::move( offset ), m_dataset->extent } }; + Offset offset( rc.m_dataset.extent.size(), 0 ); + return ChunkTable{ { std::move( offset ), rc.m_dataset.extent } }; } containingIteration().open(); Parameter< Operation::AVAILABLE_CHUNKS > param; @@ -71,4 +67,16 @@ BaseRecordComponent::availableChunks() IOHandler()->flush(); return std::move( *param.chunks ); } + +BaseRecordComponent::BaseRecordComponent( + std::shared_ptr< internal::BaseRecordComponentData > data) + : AttributableInterface{ data.get() } + , m_baseRecordComponentData{ std::move( data ) } +{} + +BaseRecordComponent::BaseRecordComponent() + : AttributableInterface{ nullptr } +{ + AttributableInterface::setData( m_baseRecordComponentData.get() ); +} } // namespace openPMD diff --git a/src/backend/PatchRecordComponent.cpp b/src/backend/PatchRecordComponent.cpp index aa97f6327d..ed705f1cc5 100644 --- a/src/backend/PatchRecordComponent.cpp +++ b/src/backend/PatchRecordComponent.cpp @@ -26,6 +26,15 @@ namespace openPMD { +namespace internal +{ + PatchRecordComponentData::PatchRecordComponentData() + { + PatchRecordComponent impl{ { this, []( auto const * ){} } }; + impl.setUnitSI( 1 ); + } +} + PatchRecordComponent& PatchRecordComponent::setUnitSI(double usi) { @@ -44,7 +53,7 @@ PatchRecordComponent::resetDataset(Dataset d) [](Extent::value_type const& i) { return i == 0u; }) ) throw std::runtime_error("Dataset extent must not be zero in any dimension."); - *m_dataset = d; + get().m_dataset = d; dirty() = true; return *this; } @@ -58,24 +67,32 @@ PatchRecordComponent::getDimensionality() const Extent PatchRecordComponent::getExtent() const { - return m_dataset->extent; + return get().m_dataset.extent; } PatchRecordComponent::PatchRecordComponent() - : m_chunks{std::make_shared< std::queue< IOTask > >()} + : BaseRecordComponent{ nullptr } +{ + BaseRecordComponent::setData( m_patchRecordComponentData ); +} + +PatchRecordComponent::PatchRecordComponent( + std::shared_ptr< internal::PatchRecordComponentData > data ) + : BaseRecordComponent{ data } + , m_patchRecordComponentData{ std::move( data ) } { - setUnitSI(1); } void PatchRecordComponent::flush(std::string const& name) { + auto & rc = get(); if(IOHandler()->m_frontendAccess == Access::READ_ONLY ) { - while( !m_chunks->empty() ) + while( !rc.m_chunks.empty() ) { - IOHandler()->enqueue(m_chunks->front()); - m_chunks->pop(); + IOHandler()->enqueue(rc.m_chunks.front()); + rc.m_chunks.pop(); } } else { @@ -86,16 +103,16 @@ PatchRecordComponent::flush(std::string const& name) dCreate.extent = getExtent(); dCreate.dtype = getDatatype(); dCreate.chunkSize = getExtent(); - dCreate.compression = m_dataset->compression; - dCreate.transform = m_dataset->transform; - dCreate.options = m_dataset->options; + dCreate.compression = rc.m_dataset.compression; + dCreate.transform = rc.m_dataset.transform; + dCreate.options = rc.m_dataset.options; IOHandler()->enqueue(IOTask(this, dCreate)); } - while( !m_chunks->empty() ) + while( !rc.m_chunks.empty() ) { - IOHandler()->enqueue(m_chunks->front()); - m_chunks->pop(); + IOHandler()->enqueue(rc.m_chunks.front()); + rc.m_chunks.pop(); } flushAttributes(); @@ -125,6 +142,7 @@ PatchRecordComponent::dirtyRecursive() const { return true; } - return !m_chunks->empty(); + auto & rc = get(); + return !rc.m_chunks.empty(); } } // openPMD From 316c9c6dca1a3c475cdf614cf6a8c25dbad33059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 30 Sep 2021 11:10:49 +0200 Subject: [PATCH 3/7] Fix invasive tests --- include/openPMD/RecordComponent.hpp | 2 ++ include/openPMD/backend/Container.hpp | 2 +- .../openPMD/backend/PatchRecordComponent.hpp | 4 ++- test/CoreTest.cpp | 32 +++++++++---------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp index c67920778e..3247062619 100644 --- a/include/openPMD/RecordComponent.hpp +++ b/include/openPMD/RecordComponent.hpp @@ -94,6 +94,8 @@ namespace internal RecordComponentData & operator=( RecordComponentData const & ) = delete; RecordComponentData & operator=( RecordComponentData && ) = delete; + OPENPMD_private: + std::queue< IOTask > m_chunks; Attribute m_constantValue{ -1 }; /** diff --git a/include/openPMD/backend/Container.hpp b/include/openPMD/backend/Container.hpp index 646573ca47..4c258f402d 100644 --- a/include/openPMD/backend/Container.hpp +++ b/include/openPMD/backend/Container.hpp @@ -367,7 +367,7 @@ class Container : public AttributableInterface flushAttributes(); } -private: +OPENPMD_private: Container() : AttributableInterface{ nullptr } { AttributableInterface::setData( m_containerData.get() ); diff --git a/include/openPMD/backend/PatchRecordComponent.hpp b/include/openPMD/backend/PatchRecordComponent.hpp index 5912f3fb2f..8504d32d74 100644 --- a/include/openPMD/backend/PatchRecordComponent.hpp +++ b/include/openPMD/backend/PatchRecordComponent.hpp @@ -45,6 +45,8 @@ namespace internal friend class openPMD::PatchRecordComponent; friend class openPMD::PatchRecordComponent; + OPENPMD_private: + std::queue< IOTask > m_chunks; PatchRecordComponentData( PatchRecordComponentData const & ) = delete; @@ -112,7 +114,7 @@ class PatchRecordComponent : public BaseRecordComponent PatchRecordComponent(); -protected: +OPENPMD_protected: PatchRecordComponent( std::shared_ptr< internal::PatchRecordComponentData > ); diff --git a/test/CoreTest.cpp b/test/CoreTest.cpp index 17095ca1b3..bc69b548fa 100644 --- a/test/CoreTest.cpp +++ b/test/CoreTest.cpp @@ -152,7 +152,7 @@ TEST_CASE( "myPath", "[core]" ) { #if openPMD_USE_INVASIVE_TESTS using vec_t = std::vector< std::string >; - auto pathOf = []( AttributableInterface & attr ) + auto pathOf = []( Attributable & attr ) { auto res = attr.myPath(); #if false @@ -748,8 +748,8 @@ TEST_CASE( "wrapper_test", "[core]" ) o.flush(); REQUIRE(all_data.get()[0] == value); #if openPMD_USE_INVASIVE_TESTS - REQUIRE(o.iterations[4].meshes["E"]["y"].m_chunks->empty()); - REQUIRE(mrc2.m_chunks->empty()); + REQUIRE(o.iterations[4].meshes["E"]["y"].get().m_chunks.empty()); + REQUIRE(mrc2.get().m_chunks.empty()); #endif MeshRecordComponent mrc3 = o.iterations[5].meshes["E"]["y"]; @@ -760,13 +760,13 @@ TEST_CASE( "wrapper_test", "[core]" ) std::shared_ptr< double > storeData = std::make_shared< double >(44); o.iterations[5].meshes["E"]["y"].storeChunk(storeData, {0}, {1}); #if openPMD_USE_INVASIVE_TESTS - REQUIRE(o.iterations[5].meshes["E"]["y"].m_chunks->size() == 1); - REQUIRE(mrc3.m_chunks->size() == 1); + REQUIRE(o.iterations[5].meshes["E"]["y"].get().m_chunks.size() == 1); + REQUIRE(mrc3.get().m_chunks.size() == 1); #endif o.flush(); #if openPMD_USE_INVASIVE_TESTS - REQUIRE(o.iterations[5].meshes["E"]["y"].m_chunks->empty()); - REQUIRE(mrc3.m_chunks->empty()); + REQUIRE(o.iterations[5].meshes["E"]["y"].get().m_chunks.empty()); + REQUIRE(mrc3.get().m_chunks.empty()); #endif o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].resetDataset(Dataset(determineDatatype< uint64_t >(), {4})); @@ -782,13 +782,13 @@ TEST_CASE( "wrapper_test", "[core]" ) size_t idx = 0; uint64_t val = 10; #if openPMD_USE_INVASIVE_TESTS - REQUIRE(o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].m_chunks->empty()); - REQUIRE(pp["numParticles"][RecordComponent::SCALAR].m_chunks->empty()); + REQUIRE(o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].get().m_chunks.empty()); + REQUIRE(pp["numParticles"][RecordComponent::SCALAR].get().m_chunks.empty()); #endif pp["numParticles"][RecordComponent::SCALAR].store(idx, val); #if openPMD_USE_INVASIVE_TESTS - REQUIRE(o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].m_chunks->size() == 1); - REQUIRE(pp["numParticles"][RecordComponent::SCALAR].m_chunks->size() == 1); + REQUIRE(o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].get().m_chunks.size() == 1); + REQUIRE(pp["numParticles"][RecordComponent::SCALAR].get().m_chunks.size() == 1); #endif std::stringstream u64str; u64str << determineDatatype(); @@ -796,13 +796,13 @@ TEST_CASE( "wrapper_test", "[core]" ) Catch::Equals("Datatypes of patch data (DOUBLE) and dataset (" + u64str.str() + ") do not match.")); o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].store(idx+1, val+1); #if openPMD_USE_INVASIVE_TESTS - REQUIRE(o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].m_chunks->size() == 2); - REQUIRE(pp["numParticles"][RecordComponent::SCALAR].m_chunks->size() == 2); + REQUIRE(o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].get().m_chunks.size() == 2); + REQUIRE(pp["numParticles"][RecordComponent::SCALAR].get().m_chunks.size() == 2); #endif o.flush(); #if openPMD_USE_INVASIVE_TESTS - REQUIRE(o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].m_chunks->empty()); - REQUIRE(pp["numParticles"][RecordComponent::SCALAR].m_chunks->empty()); + REQUIRE(o.iterations[6].particles["electrons"].particlePatches["numParticles"][RecordComponent::SCALAR].get().m_chunks.empty()); + REQUIRE(pp["numParticles"][RecordComponent::SCALAR].get().m_chunks.empty()); #endif } @@ -826,7 +826,7 @@ TEST_CASE( "use_count_test", "[core]" ) o.iterations[6].particles["electrons"]["positionOffset"][RecordComponent::SCALAR].resetDataset(dset); pprc.resetDataset(Dataset(determineDatatype(), {4})); pprc.store(0, static_cast< uint64_t >(1)); - REQUIRE(static_cast< Parameter< Operation::WRITE_DATASET >* >(pprc.m_chunks->front().parameter.get())->data.use_count() == 1); + REQUIRE(static_cast< Parameter< Operation::WRITE_DATASET >* >(pprc.get().m_chunks.front().parameter.get())->data.use_count() == 1); #endif } From 2065ec76381415a688eb54e0e865f6fd6312ccf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Tue, 28 Sep 2021 16:52:21 +0200 Subject: [PATCH 4/7] Simplify class design for Attributable and Series --- include/openPMD/IO/IOTask.hpp | 6 +- include/openPMD/Iteration.hpp | 2 +- include/openPMD/RecordComponent.hpp | 5 +- include/openPMD/Series.hpp | 223 +++++++--------- include/openPMD/backend/Attributable.hpp | 88 ++----- include/openPMD/backend/BaseRecord.hpp | 10 +- .../openPMD/backend/BaseRecordComponent.hpp | 27 +- include/openPMD/backend/Container.hpp | 14 +- .../openPMD/backend/PatchRecordComponent.hpp | 9 +- include/openPMD/backend/Writable.hpp | 4 +- src/IO/IOTask.cpp | 2 +- src/Iteration.cpp | 92 ++++--- src/Series.cpp | 247 ++++++++---------- src/backend/Attributable.cpp | 61 +++-- src/backend/BaseRecordComponent.cpp | 6 +- src/backend/Writable.cpp | 3 +- test/AuxiliaryTest.cpp | 4 +- 17 files changed, 320 insertions(+), 483 deletions(-) diff --git a/include/openPMD/IO/IOTask.hpp b/include/openPMD/IO/IOTask.hpp index 6054edbaa5..1ec4628003 100644 --- a/include/openPMD/IO/IOTask.hpp +++ b/include/openPMD/IO/IOTask.hpp @@ -37,11 +37,11 @@ namespace openPMD { -class AttributableInterface; +class Attributable; class Writable; Writable* -getWritable(AttributableInterface*); +getWritable(Attributable*); /** Type of IO operation between logical and persistent data. */ @@ -604,7 +604,7 @@ class OPENPMDAPI_EXPORT IOTask { } template< Operation op > - explicit IOTask(AttributableInterface* a, + explicit IOTask(Attributable* a, Parameter< op > const & p) : writable{getWritable(a)}, operation{op}, diff --git a/include/openPMD/Iteration.hpp b/include/openPMD/Iteration.hpp index f70e5ad2b3..b3b99138b6 100644 --- a/include/openPMD/Iteration.hpp +++ b/include/openPMD/Iteration.hpp @@ -44,7 +44,7 @@ class Iteration : public LegacyAttributable typename T_container > friend class Container; - friend class SeriesInterface; + friend class Series; friend class WriteIterations; friend class SeriesIterator; diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp index 3247062619..00d22d3520 100644 --- a/include/openPMD/RecordComponent.hpp +++ b/include/openPMD/RecordComponent.hpp @@ -84,8 +84,7 @@ namespace internal { class RecordComponentData : public BaseRecordComponentData { - friend class openPMD::RecordComponent; - + public: RecordComponentData(); RecordComponentData( RecordComponentData const & ) = delete; @@ -94,8 +93,6 @@ namespace internal RecordComponentData & operator=( RecordComponentData const & ) = delete; RecordComponentData & operator=( RecordComponentData && ) = delete; - OPENPMD_private: - std::queue< IOTask > m_chunks; Attribute m_constantValue{ -1 }; /** diff --git a/include/openPMD/Series.hpp b/include/openPMD/Series.hpp index d41e445ef3..ebc230a0c7 100644 --- a/include/openPMD/Series.hpp +++ b/include/openPMD/Series.hpp @@ -54,7 +54,7 @@ namespace openPMD { class ReadIterations; class Series; -class SeriesInterface; +class Series; namespace internal { @@ -69,14 +69,14 @@ class SeriesData : public AttributableData public: explicit SeriesData() = default; + virtual ~SeriesData(); + SeriesData( SeriesData const & ) = delete; SeriesData( SeriesData && ) = delete; SeriesData & operator=( SeriesData const & ) = delete; SeriesData & operator=( SeriesData && ) = delete; - virtual ~SeriesData() = default; - Container< Iteration, uint64_t > iterations{}; auxiliary::Option< WriteIterations > m_writeIterations; @@ -96,7 +96,8 @@ class SeriesData : public AttributableData */ StepStatus m_stepStatus = StepStatus::NoStep; bool m_parseLazily = false; - bool m_lastFlushSuccessful = true; + // only set this true after successful construction + bool m_lastFlushSuccessful = false; }; // SeriesData class SeriesInternal; @@ -109,21 +110,56 @@ class SeriesInternal; * @see https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md#hierarchy-of-the-data-file * @see https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md#iterations-and-time-series */ -class SeriesInterface : public AttributableInterface +class Series : public Attributable { - friend class AttributableInterface; + friend class Attributable; friend class Iteration; friend class Writable; friend class SeriesIterator; - friend class internal::SeriesInternal; - friend class Series; + friend class internal::SeriesData; friend class WriteIterations; protected: // Should not be called publicly, only by implementing classes - SeriesInterface( internal::SeriesData *, internal::AttributableData * ); + Series( std::shared_ptr< internal::SeriesData > ); public: + explicit Series(); + +#if openPMD_HAVE_MPI + Series( + std::string const & filepath, + Access at, + MPI_Comm comm, + std::string const & options = "{}" ); +#endif + + /** + * @brief Construct a new Series + * + * @param filepath The backend will be determined by the filepath extension. + * @param at Access mode. + * @param options Advanced backend configuration via JSON. + * May be specified as a JSON-formatted string directly, or as a path + * to a JSON textfile, prepended by an at sign '@'. + */ + Series( + std::string const & filepath, + Access at, + std::string const & options = "{}" ); + + virtual ~Series() = default; + + Container< Iteration, uint64_t > iterations; + + /** + * @brief Is this a usable Series object? + * + * @return true If a Series has been opened for reading and/or writing. + * @return false If the object has been default-constructed. + */ + operator bool() const; + /** * @return String representing the current enforced version of the openPMD standard. */ @@ -133,7 +169,7 @@ class SeriesInterface : public AttributableInterface * @param openPMD String MAJOR.MINOR.REVISION of the desired version of the openPMD standard. * @return Reference to modified series. */ - SeriesInterface& setOpenPMD(std::string const& openPMD); + Series& setOpenPMD(std::string const& openPMD); /** * @return 32-bit mask of applied extensions to the openPMD standard. @@ -144,7 +180,7 @@ class SeriesInterface : public AttributableInterface * @param openPMDextension Unsigned 32-bit integer used as a bit-mask of applied extensions. * @return Reference to modified series. */ - SeriesInterface& setOpenPMDextension(uint32_t openPMDextension); + Series& setOpenPMDextension(uint32_t openPMDextension); /** * @return String representing the common prefix for all data sets and sub-groups of a specific iteration. @@ -155,7 +191,7 @@ class SeriesInterface : public AttributableInterface * @param basePath String of the common prefix for all data sets and sub-groups of a specific iteration. * @return Reference to modified series. */ - SeriesInterface& setBasePath(std::string const& basePath); + Series& setBasePath(std::string const& basePath); /** * @throw no_such_attribute_error If optional attribute is not present. @@ -167,7 +203,7 @@ class SeriesInterface : public AttributableInterface * @param meshesPath String of the path to mesh records, relative(!) to basePath. * @return Reference to modified series. */ - SeriesInterface& setMeshesPath(std::string const& meshesPath); + Series& setMeshesPath(std::string const& meshesPath); /** * @throw no_such_attribute_error If optional attribute is not present. @@ -179,7 +215,7 @@ class SeriesInterface : public AttributableInterface * @param particlesPath String of the path to groups for each particle species, relative(!) to basePath. * @return Reference to modified series. */ - SeriesInterface& setParticlesPath(std::string const& particlesPath); + Series& setParticlesPath(std::string const& particlesPath); /** * @throw no_such_attribute_error If optional attribute is not present. @@ -191,7 +227,7 @@ class SeriesInterface : public AttributableInterface * @param author String indicating author and contact for the information in the file. * @return Reference to modified series. */ - SeriesInterface& setAuthor(std::string const& author); + Series& setAuthor(std::string const& author); /** * @throw no_such_attribute_error If optional attribute is not present. @@ -204,7 +240,7 @@ class SeriesInterface : public AttributableInterface * @param newVersion String indicating the version of the software/code/simulation that created the file. * @return Reference to modified series. */ - SeriesInterface& setSoftware(std::string const& newName, std::string const& newVersion = std::string("unspecified")); + Series& setSoftware(std::string const& newName, std::string const& newVersion = std::string("unspecified")); /** * @throw no_such_attribute_error If optional attribute is not present. @@ -219,7 +255,7 @@ class SeriesInterface : public AttributableInterface * @return Reference to modified series. */ [[deprecated("Set the version with the second argument of setSoftware()")]] - SeriesInterface& setSoftwareVersion(std::string const& softwareVersion); + Series& setSoftwareVersion(std::string const& softwareVersion); /** * @throw no_such_attribute_error If optional attribute is not present. @@ -231,7 +267,7 @@ class SeriesInterface : public AttributableInterface * @param date String indicating the date of creation. * @return Reference to modified series. */ - SeriesInterface& setDate(std::string const& date); + Series& setDate(std::string const& date); /** * @throw no_such_attribute_error If optional attribute is not present. @@ -243,7 +279,7 @@ class SeriesInterface : public AttributableInterface * @param newSoftwareDependencies String indicating dependencies of software that were used to create the file (semicolon-separated list if needed). * @return Reference to modified series. */ - SeriesInterface& setSoftwareDependencies(std::string const& newSoftwareDependencies); + Series& setSoftwareDependencies(std::string const& newSoftwareDependencies); /** * @throw no_such_attribute_error If optional attribute is not present. @@ -255,7 +291,7 @@ class SeriesInterface : public AttributableInterface * @param newMachine String indicating the machine or relevant hardware that created the file (semicolon-separated list if needed).. * @return Reference to modified series. */ - SeriesInterface& setMachine(std::string const& newMachine); + Series& setMachine(std::string const& newMachine); /** * @return Current encoding style for multiple iterations in this series. @@ -269,7 +305,7 @@ class SeriesInterface : public AttributableInterface * @param iterationEncoding Desired encoding style for multiple iterations in this series. * @return Reference to modified series. */ - SeriesInterface& setIterationEncoding(IterationEncoding iterationEncoding); + Series& setIterationEncoding(IterationEncoding iterationEncoding); /** * @return String describing a pattern describing how to access single iterations in the raw file. @@ -285,7 +321,7 @@ class SeriesInterface : public AttributableInterface * The format depends on the selected iterationEncoding method. * @return Reference to modified series. */ - SeriesInterface& setIterationFormat(std::string const& iterationFormat); + Series& setIterationFormat(std::string const& iterationFormat); /** * @return String of a pattern for file names. @@ -297,7 +333,7 @@ class SeriesInterface : public AttributableInterface * @param name String of the pattern for file names. Must include iteration regex \%T for fileBased data. * @return Reference to modified series. */ - SeriesInterface& setName(std::string const& name); + Series& setName(std::string const& name); /** The currently used backend * @@ -311,6 +347,32 @@ class SeriesInterface : public AttributableInterface */ void flush(); + /** + * @brief Entry point to the reading end of the streaming API. + * + * Creates and returns an instance of the ReadIterations class which can + * be used for iterating over the openPMD iterations in a C++11-style for + * loop. + * Look for the ReadIterations class for further documentation. + * + * @return ReadIterations + */ + ReadIterations readIterations(); + + /** + * @brief Entry point to the writing end of the streaming API. + * + * Creates and returns an instance of the WriteIterations class which is a + * restricted container of iterations which takes care of + * streaming semantics. + * The created object is stored as member of the Series object, hence this + * method may be called as many times as a user wishes. + * Look for the WriteIterations class for further documentation. + * + * @return WriteIterations + */ + WriteIterations writeIterations(); + OPENPMD_private: static constexpr char const * const BASEPATH = "/data/%T/"; @@ -318,7 +380,7 @@ class SeriesInterface : public AttributableInterface using iterations_t = decltype(internal::SeriesData::iterations); using iterations_iterator = iterations_t::iterator; - internal::SeriesData * m_series = nullptr; + std::shared_ptr< internal::SeriesData > m_series = nullptr; inline internal::SeriesData & get() { @@ -436,118 +498,9 @@ class SeriesInterface : public AttributableInterface internal::AttributableData & file, iterations_iterator it, Iteration & iteration ); -}; // SeriesInterface - -namespace internal -{ -class SeriesInternal : public SeriesData, public SeriesInterface -{ - friend struct SeriesShared; - friend class openPMD::Iteration; - friend class openPMD::Series; - friend class openPMD::Writable; - -public: -#if openPMD_HAVE_MPI - SeriesInternal( - std::string const & filepath, - Access at, - MPI_Comm comm, - std::string const & options = "{}" ); -#endif - - SeriesInternal( - std::string const & filepath, - Access at, - std::string const & options = "{}" ); - // @todo make AttributableInterface<>::linkHierarchy non-virtual - virtual ~SeriesInternal(); -}; -} // namespace internal - -/** @brief Root level of the openPMD hierarchy. - * - * Entry point and common link between all iterations of particle and mesh data. - * - * An instance can be created either directly via the given constructors or via - * the SeriesBuilder class. - * - * @see https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md#hierarchy-of-the-data-file - * @see https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md#iterations-and-time-series - */ -class Series : public SeriesInterface -{ -private: - std::shared_ptr< internal::SeriesInternal > m_series; - - // constructor from private parts - Series( std::shared_ptr< internal::SeriesInternal > ); - -public: - explicit Series(); - -#if openPMD_HAVE_MPI - Series( - std::string const & filepath, - Access at, - MPI_Comm comm, - std::string const & options = "{}" ); -#endif - - /** - * @brief Construct a new Series - * - * @param filepath The backend will be determined by the filepath extension. - * @param at Access mode. - * @param options Advanced backend configuration via JSON. - * May be specified as a JSON-formatted string directly, or as a path - * to a JSON textfile, prepended by an at sign '@'. - */ - Series( - std::string const & filepath, - Access at, - std::string const & options = "{}" ); - - virtual ~Series() = default; - - Container< Iteration, uint64_t > iterations; - - /** - * @brief Is this a usable Series object? - * - * @return true If a Series has been opened for reading and/or writing. - * @return false If the object has been default-constructed. - */ - operator bool() const; - - /** - * @brief Entry point to the reading end of the streaming API. - * - * Creates and returns an instance of the ReadIterations class which can - * be used for iterating over the openPMD iterations in a C++11-style for - * loop. - * Look for the ReadIterations class for further documentation. - * - * @return ReadIterations - */ - ReadIterations readIterations(); - - /** - * @brief Entry point to the writing end of the streaming API. - * - * Creates and returns an instance of the WriteIterations class which is a - * restricted container of iterations which takes care of - * streaming semantics. - * The created object is stored as member of the Series object, hence this - * method may be called as many times as a user wishes. - * Look for the WriteIterations class for further documentation. - * - * @return WriteIterations - */ - WriteIterations writeIterations(); -}; +}; // Series } // namespace openPMD // Make sure that this one is always included if Series.hpp is included, -// otherwise SeriesInterface::readIterations() cannot be used +// otherwise Series::readIterations() cannot be used #include "openPMD/ReadIterations.hpp" diff --git a/include/openPMD/backend/Attributable.hpp b/include/openPMD/backend/Attributable.hpp index 7d37406fef..1198dc9496 100644 --- a/include/openPMD/backend/Attributable.hpp +++ b/include/openPMD/backend/Attributable.hpp @@ -47,12 +47,9 @@ namespace traits struct GenerationPolicy; } // traits class AbstractFilePosition; -class AttributableInterface; +class Attributable; class Iteration; -namespace internal -{ - class SeriesInternal; -} +class Series; class no_such_attribute_error : public std::runtime_error { @@ -67,7 +64,7 @@ namespace internal { class AttributableData { - friend class openPMD::AttributableInterface; + friend class openPMD::Attributable; public: AttributableData(); @@ -113,11 +110,11 @@ template< typename > class BaseRecordData; * Mandatory and user-defined Attributes and their data for every object in the * openPMD hierarchy are stored and managed through this class. */ -class AttributableInterface +class Attributable { // @todo remove unnecessary friend (wew that sounds bitter) using A_MAP = std::map< std::string, Attribute >; - friend Writable* getWritable(AttributableInterface*); + friend Writable* getWritable(Attributable*); template< typename T_elem > friend class BaseRecord; template< typename T_elem > @@ -134,24 +131,21 @@ class AttributableInterface friend struct traits::GenerationPolicy; friend class Iteration; friend class Series; - friend class SeriesInterface; + friend class Series; friend class Writable; friend class WriteIterations; protected: - internal::AttributableData * m_attri = nullptr; + std::shared_ptr< internal::AttributableData > m_attri{ + new internal::AttributableData() }; // Should not be called publicly, only by implementing classes - AttributableInterface( internal::AttributableData * ); - template< typename T > - AttributableInterface( T * attri ) - : AttributableInterface{ - static_cast< internal::AttributableData * >( attri ) } - { - } + Attributable( std::shared_ptr< internal::AttributableData > ); public: - virtual ~AttributableInterface() = default; + Attributable(); + + virtual ~Attributable() = default; /** Populate Attribute of provided name with provided value. * @@ -216,7 +210,7 @@ class AttributableInterface * @param comment String value to be stored as a comment. * @return Reference to modified Attributable. */ - AttributableInterface& setComment(std::string const& comment); + Attributable& setComment(std::string const& comment); /** Flush the corresponding Series object * @@ -260,8 +254,7 @@ class AttributableInterface OPENPMD_protected: - internal::SeriesInternal const & retrieveSeries() const; - internal::SeriesInternal & retrieveSeries(); + Series retrieveSeries() const; /** Returns the corresponding Iteration * @@ -357,38 +350,20 @@ class AttributableInterface } inline - void setData( internal::AttributableData * attri ) + void setData( std::shared_ptr< internal::AttributableData > attri ) { - m_attri = attri; + m_attri = std::move( attri ); } inline internal::AttributableData & get() { - if( m_attri ) - { - return *m_attri; - } - else - { - throw std::runtime_error( - "[AttributableInterface] " - "Cannot use default-constructed Attributable." ); - } + return *m_attri; } inline internal::AttributableData const & get() const { - if( m_attri ) - { - return *m_attri; - } - else - { - throw std::runtime_error( - "[AttributableInterface] " - "Cannot use default-constructed Attributable." ); - } + return *m_attri; } bool dirty() const { return writable().dirty; } @@ -403,29 +378,14 @@ class AttributableInterface * @param w The Writable representing the parent. */ virtual void linkHierarchy(Writable& w); -}; // AttributableInterface +}; // Attributable -// Alias this as Attributable since this is a public abstract parent class -// for most of the classes in our object model of the openPMD hierarchy -using Attributable = AttributableInterface; - -class LegacyAttributable : public AttributableInterface -{ -protected: - std::shared_ptr< internal::AttributableData > m_attributableData = - std::make_shared< internal::AttributableData >(); - -public: - LegacyAttributable() : AttributableInterface{ nullptr } - { - AttributableInterface::setData( m_attributableData.get() ); - } -}; +using LegacyAttributable = Attributable; //TODO explicitly instantiate Attributable::setAttribute for all T in Datatype template< typename T > inline bool -AttributableInterface::setAttribute( std::string const & key, T value ) +Attributable::setAttribute( std::string const & key, T value ) { internal::attr_value_check( key, value ); @@ -462,13 +422,13 @@ AttributableInterface::setAttribute( std::string const & key, T value ) } inline bool -AttributableInterface::setAttribute( std::string const & key, char const value[] ) +Attributable::setAttribute( std::string const & key, char const value[] ) { return this->setAttribute(key, std::string(value)); } template< typename T > -inline T AttributableInterface::readFloatingpoint( std::string const & key ) const +inline T Attributable::readFloatingpoint( std::string const & key ) const { static_assert(std::is_floating_point< T >::value, "Type of attribute must be floating point"); @@ -477,7 +437,7 @@ inline T AttributableInterface::readFloatingpoint( std::string const & key ) con template< typename T > inline std::vector< T > -AttributableInterface::readVectorFloatingpoint( std::string const & key ) const +Attributable::readVectorFloatingpoint( std::string const & key ) const { static_assert(std::is_floating_point< T >::value, "Type of attribute must be floating point"); diff --git a/include/openPMD/backend/BaseRecord.hpp b/include/openPMD/backend/BaseRecord.hpp index 065e7158c9..dd4328e047 100644 --- a/include/openPMD/backend/BaseRecord.hpp +++ b/include/openPMD/backend/BaseRecord.hpp @@ -31,20 +31,14 @@ namespace openPMD { - -template< typename > class BaseRecord; - namespace internal { template< typename T_elem > class BaseRecordData : public ContainerData< T_elem > { - template< typename > friend class openPMD::BaseRecord; - - protected: + public: bool m_containsScalar = false; - public: BaseRecordData(); BaseRecordData( BaseRecordData const & ) = delete; @@ -168,7 +162,7 @@ namespace internal template< typename T_elem > BaseRecordData< T_elem >::BaseRecordData() { - AttributableInterface impl{ this }; + Attributable impl{ { this, []( auto const * ){} } }; impl.setAttribute( "unitDimension", std::array< double, 7 >{ { 0., 0., 0., 0., 0., 0., 0. } } ); diff --git a/include/openPMD/backend/BaseRecordComponent.hpp b/include/openPMD/backend/BaseRecordComponent.hpp index 693159dd2d..2dbffafd94 100644 --- a/include/openPMD/backend/BaseRecordComponent.hpp +++ b/include/openPMD/backend/BaseRecordComponent.hpp @@ -32,31 +32,11 @@ namespace openPMD { -class BaseRecordComponent; -class Iteration; -class Mesh; -class ParticleSpecies; -class PatchRecordComponent; -class PatchRecordComponentInterface; -class Record; -class RecordComponent; -class RecordComponentInterface; - namespace internal { class BaseRecordComponentData : public AttributableData { - friend class openPMD::BaseRecordComponent; - friend class openPMD::Iteration; - friend class openPMD::Mesh; - friend class openPMD::ParticleSpecies; - friend class openPMD::PatchRecordComponent; - friend class openPMD::PatchRecordComponentInterface; - friend class openPMD::Record; - friend class openPMD::RecordComponent; - friend class openPMD::RecordComponentInterface; - - + public: Dataset m_dataset{ Datatype::UNDEFINED, {} }; bool m_isConstant = false; @@ -68,12 +48,11 @@ namespace internal BaseRecordComponentData & operator=( BaseRecordComponentData && ) = delete; - protected: BaseRecordComponentData() = default; }; } -class BaseRecordComponent : public AttributableInterface +class BaseRecordComponent : public Attributable { template< typename T, @@ -140,7 +119,7 @@ class BaseRecordComponent : public AttributableInterface std::shared_ptr< internal::BaseRecordComponentData > data ) { m_baseRecordComponentData = std::move( data ); - AttributableInterface::setData( m_baseRecordComponentData.get() ); + Attributable::setData( m_baseRecordComponentData ); } BaseRecordComponent( std::shared_ptr< internal::BaseRecordComponentData > ); diff --git a/include/openPMD/backend/Container.hpp b/include/openPMD/backend/Container.hpp index 4c258f402d..45e3c836df 100644 --- a/include/openPMD/backend/Container.hpp +++ b/include/openPMD/backend/Container.hpp @@ -124,17 +124,17 @@ template< typename T, typename T_key = std::string, typename T_container = std::map< T_key, T > > -class Container : public AttributableInterface +class Container : public Attributable { static_assert( - std::is_base_of< AttributableInterface, T >::value, + std::is_base_of< Attributable, T >::value, "Type of container element must be derived from Writable"); friend class Iteration; friend class ParticleSpecies; friend class ParticlePatches; friend class internal::SeriesData; - friend class SeriesInterface; + friend class Series; friend class Series; template< typename > friend class internal::EraseStaleEntries; @@ -148,7 +148,7 @@ class Container : public AttributableInterface inline void setData( std::shared_ptr< ContainerData > containerData ) { m_containerData = std::move( containerData ); - AttributableInterface::setData( m_containerData.get() ); + Attributable::setData( m_containerData ); } inline InternalContainer const & container() const @@ -342,7 +342,7 @@ class Container : public AttributableInterface OPENPMD_protected: Container( std::shared_ptr< ContainerData > containerData ) - : AttributableInterface{ containerData.get() } + : Attributable{ containerData } , m_containerData{ std::move( containerData ) } { } @@ -368,9 +368,9 @@ class Container : public AttributableInterface } OPENPMD_private: - Container() : AttributableInterface{ nullptr } + Container() : Attributable{ nullptr } { - AttributableInterface::setData( m_containerData.get() ); + Attributable::setData( m_containerData ); } }; diff --git a/include/openPMD/backend/PatchRecordComponent.hpp b/include/openPMD/backend/PatchRecordComponent.hpp index 8504d32d74..cf3989b6af 100644 --- a/include/openPMD/backend/PatchRecordComponent.hpp +++ b/include/openPMD/backend/PatchRecordComponent.hpp @@ -35,17 +35,11 @@ namespace openPMD { - -class PatchRecordComponent; -class PatchRecordComponent; namespace internal { class PatchRecordComponentData : public BaseRecordComponentData { - friend class openPMD::PatchRecordComponent; - friend class openPMD::PatchRecordComponent; - - OPENPMD_private: + public: std::queue< IOTask > m_chunks; @@ -57,7 +51,6 @@ namespace internal PatchRecordComponentData & operator=( PatchRecordComponentData && ) = delete; - protected: PatchRecordComponentData(); }; } diff --git a/include/openPMD/backend/Writable.hpp b/include/openPMD/backend/Writable.hpp index 013b2223c9..be1c6167bb 100644 --- a/include/openPMD/backend/Writable.hpp +++ b/include/openPMD/backend/Writable.hpp @@ -64,7 +64,7 @@ class AttributableData; class Writable final { friend class internal::AttributableData; - friend class AttributableInterface; + friend class Attributable; template< typename T_elem > friend class BaseRecord; template< typename T_elem > @@ -78,7 +78,7 @@ class Writable final friend class Iteration; friend class Mesh; friend class ParticleSpecies; - friend class SeriesInterface; + friend class Series; friend class Record; template< typename > friend class CommonADIOS1IOHandlerImpl; friend class ADIOS1IOHandlerImpl; diff --git a/src/IO/IOTask.cpp b/src/IO/IOTask.cpp index 0d46627820..5ff2a31b8d 100644 --- a/src/IO/IOTask.cpp +++ b/src/IO/IOTask.cpp @@ -25,6 +25,6 @@ namespace openPMD { Writable* -getWritable(AttributableInterface* a) +getWritable(Attributable* a) { return &a->writable(); } } // openPMD diff --git a/src/Iteration.cpp b/src/Iteration.cpp index 405e680c2f..4e4290349c 100644 --- a/src/Iteration.cpp +++ b/src/Iteration.cpp @@ -120,13 +120,13 @@ Iteration::close( bool _flush ) else { // flush things manually - internal::SeriesInternal * s = &retrieveSeries(); + Series s = retrieveSeries(); // figure out my iteration number - auto begin = s->indexOf( *this ); + auto begin = s.indexOf( *this ); auto end = begin; ++end; - s->flush_impl( begin, end, FlushLevel::UserFlush ); + s.flush_impl( begin, end, FlushLevel::UserFlush ); } } else @@ -148,10 +148,10 @@ Iteration::open() *m_closed = CloseStatus::Open; } runDeferredParseAccess(); - internal::SeriesInternal * s = &retrieveSeries(); + Series s = retrieveSeries(); // figure out my iteration number - auto begin = s->indexOf( *this ); - s->openIteration( begin->first, *this ); + auto begin = s.indexOf( *this ); + s.openIteration( begin->first, *this ); IOHandler()->flush(); return *this; } @@ -196,21 +196,19 @@ Iteration::flushFileBased(std::string const& filename, uint64_t i) { /* Find the root point [Series] of this file, * meshesPath and particlesPath are stored there */ - internal::SeriesInternal * s = &retrieveSeries(); - if( s == nullptr ) - throw std::runtime_error("[Iteration::flushFileBased] Series* is a nullptr"); + Series s = retrieveSeries(); if( !written() ) { /* create file */ Parameter< Operation::CREATE_FILE > fCreate; fCreate.name = filename; - IOHandler()->enqueue(IOTask(s, fCreate)); + IOHandler()->enqueue(IOTask(&s.writable(), fCreate)); /* create basePath */ Parameter< Operation::CREATE_PATH > pCreate; - pCreate.path = auxiliary::replace_first(s->basePath(), "%T/", ""); - IOHandler()->enqueue(IOTask(&s->iterations, pCreate)); + pCreate.path = auxiliary::replace_first(s.basePath(), "%T/", ""); + IOHandler()->enqueue(IOTask(&s.iterations, pCreate)); /* create iteration path */ pCreate.path = std::to_string(i); @@ -224,7 +222,7 @@ Iteration::flushFileBased(std::string const& filename, uint64_t i) Parameter< Operation::OPEN_FILE > fOpen; fOpen.name = filename; fOpen.encoding = IterationEncoding::fileBased; - IOHandler()->enqueue(IOTask(s, fOpen)); + IOHandler()->enqueue(IOTask(&s.writable(), fOpen)); flush(); return; @@ -232,7 +230,7 @@ Iteration::flushFileBased(std::string const& filename, uint64_t i) // operations for read/read-write mode /* open file */ - s->openIteration( i, *this ); + s.openIteration( i, *this ); } flush(); @@ -280,16 +278,16 @@ Iteration::flush() { /* Find the root point [Series] of this file, * meshesPath and particlesPath are stored there */ - internal::SeriesInternal * s = &retrieveSeries(); + Series s = retrieveSeries(); - if( !meshes.empty() || s->containsAttribute("meshesPath") ) + if( !meshes.empty() || s.containsAttribute("meshesPath") ) { - if( !s->containsAttribute("meshesPath") ) + if( !s.containsAttribute("meshesPath") ) { - s->setMeshesPath("meshes/"); - s->flushMeshesPath(); + s.setMeshesPath("meshes/"); + s.flushMeshesPath(); } - meshes.flush(s->meshesPath()); + meshes.flush(s.meshesPath()); for( auto& m : meshes ) m.second.flush(m.first); } @@ -298,14 +296,14 @@ Iteration::flush() meshes.dirty() = false; } - if( !particles.empty() || s->containsAttribute("particlesPath") ) + if( !particles.empty() || s.containsAttribute("particlesPath") ) { - if( !s->containsAttribute("particlesPath") ) + if( !s.containsAttribute("particlesPath") ) { - s->setParticlesPath("particles/"); - s->flushParticlesPath(); + s.setParticlesPath("particles/"); + s.flushParticlesPath(); } - particles.flush(s->particlesPath()); + particles.flush(s.particlesPath()); for( auto& species : particles ) species.second.flush(species.first); } @@ -357,7 +355,7 @@ void Iteration::reread( std::string const & path ) void Iteration::readFileBased( std::string filePath, std::string const & groupPath ) { - auto & series = retrieveSeries(); + auto series = retrieveSeries(); series.readOneIterationFileBased( filePath ); @@ -413,10 +411,10 @@ void Iteration::read_impl( std::string const & groupPath ) /* Find the root point [Series] of this file, * meshesPath and particlesPath are stored there */ - internal::SeriesInternal * s = &retrieveSeries(); + Series s = retrieveSeries(); Parameter< Operation::LIST_PATHS > pList; - std::string version = s->openPMD(); + std::string version = s.openPMD(); bool hasMeshes = false; bool hasParticles = false; if( version == "1.0.0" || version == "1.0.1" ) @@ -426,23 +424,23 @@ void Iteration::read_impl( std::string const & groupPath ) hasMeshes = std::count( pList.paths->begin(), pList.paths->end(), - auxiliary::replace_last(s->meshesPath(), "/", "") + auxiliary::replace_last(s.meshesPath(), "/", "") ) == 1; hasParticles = std::count( pList.paths->begin(), pList.paths->end(), - auxiliary::replace_last(s->particlesPath(), "/", "") + auxiliary::replace_last(s.particlesPath(), "/", "") ) == 1; pList.paths->clear(); } else { - hasMeshes = s->containsAttribute("meshesPath"); - hasParticles = s->containsAttribute("particlesPath"); + hasMeshes = s.containsAttribute("meshesPath"); + hasParticles = s.containsAttribute("particlesPath"); } if( hasMeshes ) { - pOpen.path = s->meshesPath(); + pOpen.path = s.meshesPath(); IOHandler()->enqueue(IOTask(&meshes, pOpen)); meshes.readAttributes( ReadMode::FullyReread ); @@ -507,7 +505,7 @@ void Iteration::read_impl( std::string const & groupPath ) if( hasParticles ) { - pOpen.path = s->particlesPath(); + pOpen.path = s.particlesPath(); IOHandler()->enqueue(IOTask(&particles, pOpen)); particles.readAttributes( ReadMode::FullyReread ); @@ -539,18 +537,18 @@ AdvanceStatus Iteration::beginStep() { using IE = IterationEncoding; - auto & series = retrieveSeries(); + auto series = retrieveSeries(); // Initialize file with this to quiet warnings // The following switch is comprehensive internal::AttributableData * file = nullptr; switch( series.iterationEncoding() ) { case IE::fileBased: - file = m_attributableData.get(); + file = &Attributable::get(); break; case IE::groupBased: case IE::variableBased: - file = &series; + file = &series.get(); break; } AdvanceStatus status = series.advance( @@ -584,18 +582,18 @@ void Iteration::endStep() { using IE = IterationEncoding; - auto & series = retrieveSeries(); + auto series = retrieveSeries(); // Initialize file with this to quiet warnings // The following switch is comprehensive internal::AttributableData * file = nullptr; switch( series.iterationEncoding() ) { case IE::fileBased: - file = m_attributableData.get(); + file = &Attributable::get(); break; case IE::groupBased: case IE::variableBased: - file = &series; + file = &series.get(); break; } // @todo filebased check @@ -606,15 +604,15 @@ Iteration::endStep() StepStatus Iteration::getStepStatus() { - internal::SeriesInternal * s = &retrieveSeries(); - switch( s->iterationEncoding() ) + Series s = retrieveSeries(); + switch( s.iterationEncoding() ) { using IE = IterationEncoding; case IE::fileBased: return *this->m_stepStatus; case IE::groupBased: case IE::variableBased: - return s->m_stepStatus; + return s.get().m_stepStatus; default: throw std::runtime_error( "[Iteration] unreachable" ); } @@ -623,8 +621,8 @@ Iteration::getStepStatus() void Iteration::setStepStatus( StepStatus status ) { - internal::SeriesInternal * s = &retrieveSeries(); - switch( s->iterationEncoding() ) + Series s = retrieveSeries(); + switch( s.iterationEncoding() ) { using IE = IterationEncoding; case IE::fileBased: @@ -632,7 +630,7 @@ Iteration::setStepStatus( StepStatus status ) break; case IE::groupBased: case IE::variableBased: - s->m_stepStatus = status; + s.get().m_stepStatus = status; break; default: throw std::runtime_error( "[Iteration] unreachable" ); @@ -670,7 +668,7 @@ Iteration::dirtyRecursive() const void Iteration::linkHierarchy(Writable& w) { - AttributableInterface::linkHierarchy(w); + Attributable::linkHierarchy(w); meshes.linkHierarchy(this->writable()); particles.linkHierarchy(this->writable()); } diff --git a/src/Series.cpp b/src/Series.cpp index 52836e32f2..abc1f8a797 100644 --- a/src/Series.cpp +++ b/src/Series.cpp @@ -80,7 +80,7 @@ namespace matcher(std::string const &prefix, int padding, std::string const &postfix, Format f); } // namespace [anonymous] -struct SeriesInterface::ParsedInput +struct Series::ParsedInput { std::string path; std::string name; @@ -91,47 +91,40 @@ struct SeriesInterface::ParsedInput int filenamePadding = -1; }; //ParsedInput -SeriesInterface::SeriesInterface( - internal::SeriesData * series, internal::AttributableData * attri ) - : AttributableInterface{ attri } - , m_series{ series } -{ -} - std::string -SeriesInterface::openPMD() const +Series::openPMD() const { return getAttribute("openPMD").get< std::string >(); } -SeriesInterface& -SeriesInterface::setOpenPMD(std::string const& o) +Series& +Series::setOpenPMD(std::string const& o) { setAttribute("openPMD", o); return *this; } uint32_t -SeriesInterface::openPMDextension() const +Series::openPMDextension() const { return getAttribute("openPMDextension").get< uint32_t >(); } -SeriesInterface& -SeriesInterface::setOpenPMDextension(uint32_t oe) +Series& +Series::setOpenPMDextension(uint32_t oe) { setAttribute("openPMDextension", oe); return *this; } std::string -SeriesInterface::basePath() const +Series::basePath() const { return getAttribute("basePath").get< std::string >(); } -SeriesInterface& -SeriesInterface::setBasePath(std::string const& bp) +Series& +Series::setBasePath(std::string const& bp) { std::string version = openPMD(); if( version == "1.0.0" || version == "1.0.1" || version == "1.1.0" ) @@ -142,13 +135,13 @@ SeriesInterface::setBasePath(std::string const& bp) } std::string -SeriesInterface::meshesPath() const +Series::meshesPath() const { return getAttribute("meshesPath").get< std::string >(); } -SeriesInterface& -SeriesInterface::setMeshesPath(std::string const& mp) +Series& +Series::setMeshesPath(std::string const& mp) { auto & series = get(); if( std::any_of(series.iterations.begin(), series.iterations.end(), @@ -164,13 +157,13 @@ SeriesInterface::setMeshesPath(std::string const& mp) } std::string -SeriesInterface::particlesPath() const +Series::particlesPath() const { return getAttribute("particlesPath").get< std::string >(); } -SeriesInterface& -SeriesInterface::setParticlesPath(std::string const& pp) +Series& +Series::setParticlesPath(std::string const& pp) { auto & series = get(); if( std::any_of(series.iterations.begin(), series.iterations.end(), @@ -186,26 +179,26 @@ SeriesInterface::setParticlesPath(std::string const& pp) } std::string -SeriesInterface::author() const +Series::author() const { return getAttribute("author").get< std::string >(); } -SeriesInterface& -SeriesInterface::setAuthor(std::string const& a) +Series& +Series::setAuthor(std::string const& a) { setAttribute("author", a); return *this; } std::string -SeriesInterface::software() const +Series::software() const { return getAttribute("software").get< std::string >(); } -SeriesInterface& -SeriesInterface::setSoftware( std::string const& newName, std::string const& newVersion ) +Series& +Series::setSoftware( std::string const& newName, std::string const& newVersion ) { setAttribute( "software", newName ); setAttribute( "softwareVersion", newVersion ); @@ -213,65 +206,65 @@ SeriesInterface::setSoftware( std::string const& newName, std::string const& new } std::string -SeriesInterface::softwareVersion() const +Series::softwareVersion() const { return getAttribute("softwareVersion").get< std::string >(); } -SeriesInterface& -SeriesInterface::setSoftwareVersion(std::string const& sv) +Series& +Series::setSoftwareVersion(std::string const& sv) { setAttribute("softwareVersion", sv); return *this; } std::string -SeriesInterface::date() const +Series::date() const { return getAttribute("date").get< std::string >(); } -SeriesInterface& -SeriesInterface::setDate(std::string const& d) +Series& +Series::setDate(std::string const& d) { setAttribute("date", d); return *this; } std::string -SeriesInterface::softwareDependencies() const +Series::softwareDependencies() const { return getAttribute("softwareDependencies").get< std::string >(); } -SeriesInterface& -SeriesInterface::setSoftwareDependencies(std::string const &newSoftwareDependencies) +Series& +Series::setSoftwareDependencies(std::string const &newSoftwareDependencies) { setAttribute("softwareDependencies", newSoftwareDependencies); return *this; } std::string -SeriesInterface::machine() const +Series::machine() const { return getAttribute("machine").get< std::string >(); } -SeriesInterface& -SeriesInterface::setMachine(std::string const &newMachine) +Series& +Series::setMachine(std::string const &newMachine) { setAttribute("machine", newMachine); return *this; } IterationEncoding -SeriesInterface::iterationEncoding() const +Series::iterationEncoding() const { return get().m_iterationEncoding; } -SeriesInterface& -SeriesInterface::setIterationEncoding(IterationEncoding ie) +Series& +Series::setIterationEncoding(IterationEncoding ie) { auto & series = get(); if( written() ) @@ -310,13 +303,13 @@ SeriesInterface::setIterationEncoding(IterationEncoding ie) } std::string -SeriesInterface::iterationFormat() const +Series::iterationFormat() const { return getAttribute("iterationFormat").get< std::string >(); } -SeriesInterface& -SeriesInterface::setIterationFormat(std::string const& i) +Series& +Series::setIterationFormat(std::string const& i) { if( written() ) throw std::runtime_error("A files iterationFormat can not (yet) be changed after it has been written."); @@ -331,13 +324,13 @@ SeriesInterface::setIterationFormat(std::string const& i) } std::string -SeriesInterface::name() const +Series::name() const { return get().m_name; } -SeriesInterface& -SeriesInterface::setName(std::string const& n) +Series& +Series::setName(std::string const& n) { auto & series = get(); if( written() ) @@ -371,13 +364,13 @@ SeriesInterface::setName(std::string const& n) } std::string -SeriesInterface::backend() const +Series::backend() const { return IOHandler()->backendName(); } void -SeriesInterface::flush() +Series::flush() { auto & series = get(); flush_impl( @@ -386,10 +379,10 @@ SeriesInterface::flush() FlushLevel::UserFlush ); } -std::unique_ptr< SeriesInterface::ParsedInput > -SeriesInterface::parseInput(std::string filepath) +std::unique_ptr< Series::ParsedInput > +Series::parseInput(std::string filepath) { - std::unique_ptr< SeriesInterface::ParsedInput > input{new SeriesInterface::ParsedInput}; + std::unique_ptr< Series::ParsedInput > input{new Series::ParsedInput}; #ifdef _WIN32 if( auxiliary::contains(filepath, '/') ) @@ -450,13 +443,13 @@ SeriesInterface::parseInput(std::string filepath) return input; } -bool SeriesInterface::hasExpansionPattern( std::string filenameWithExtension ) +bool Series::hasExpansionPattern( std::string filenameWithExtension ) { auto input = parseInput( std::move( filenameWithExtension ) ); return input->iterationEncoding == IterationEncoding::fileBased; } -bool SeriesInterface::reparseExpansionPattern( +bool Series::reparseExpansionPattern( std::string filenameWithExtension ) { auto input = parseInput( std::move( filenameWithExtension ) ); @@ -471,9 +464,9 @@ bool SeriesInterface::reparseExpansionPattern( return true; } -void SeriesInterface::init( +void Series::init( std::shared_ptr< AbstractIOHandler > ioHandler, - std::unique_ptr< SeriesInterface::ParsedInput > input ) + std::unique_ptr< Series::ParsedInput > input ) { auto & series = get(); writable().IOHandler = ioHandler; @@ -519,10 +512,11 @@ void SeriesInterface::init( initDefaults( input->iterationEncoding ); setIterationEncoding(input->iterationEncoding); } + series.m_lastFlushSuccessful = true; } void -SeriesInterface::initDefaults( IterationEncoding ie ) +Series::initDefaults( IterationEncoding ie ) { if( !containsAttribute("openPMD")) setOpenPMD( getStandard() ); @@ -547,7 +541,7 @@ SeriesInterface::initDefaults( IterationEncoding ie ) } std::future< void > -SeriesInterface::flush_impl( +Series::flush_impl( iterations_iterator begin, iterations_iterator end, FlushLevel level, @@ -590,7 +584,7 @@ SeriesInterface::flush_impl( } void -SeriesInterface::flushFileBased( iterations_iterator begin, iterations_iterator end ) +Series::flushFileBased( iterations_iterator begin, iterations_iterator end ) { auto & series = get(); if( end == begin ) @@ -677,7 +671,7 @@ SeriesInterface::flushFileBased( iterations_iterator begin, iterations_iterator } void -SeriesInterface::flushGorVBased( iterations_iterator begin, iterations_iterator end ) +Series::flushGorVBased( iterations_iterator begin, iterations_iterator end ) { auto & series = get(); if( IOHandler()->m_frontendAccess == Access::READ_ONLY ) @@ -762,7 +756,7 @@ SeriesInterface::flushGorVBased( iterations_iterator begin, iterations_iterator } void -SeriesInterface::flushMeshesPath() +Series::flushMeshesPath() { Parameter< Operation::WRITE_ATT > aWrite; aWrite.name = "meshesPath"; @@ -773,7 +767,7 @@ SeriesInterface::flushMeshesPath() } void -SeriesInterface::flushParticlesPath() +Series::flushParticlesPath() { Parameter< Operation::WRITE_ATT > aWrite; aWrite.name = "particlesPath"; @@ -784,7 +778,7 @@ SeriesInterface::flushParticlesPath() } void -SeriesInterface::readFileBased( ) +Series::readFileBased( ) { auto & series = get(); Parameter< Operation::OPEN_FILE > fOpen; @@ -819,7 +813,7 @@ SeriesInterface::readFileBased( ) if( series.iterations.empty() ) { - /* Frontend access type might change during SeriesInterface::read() to allow parameter modification. + /* Frontend access type might change during Series::read() to allow parameter modification. * Backend access type stays unchanged for the lifetime of a Series. */ if(IOHandler()->m_backendAccess == Access::READ_ONLY ) throw no_such_file_error("No matching iterations found: " + name()); @@ -859,14 +853,14 @@ SeriesInterface::readFileBased( ) if( paddings.size() == 1u ) series.m_filenamePadding = *paddings.begin(); - /* Frontend access type might change during SeriesInterface::read() to allow parameter modification. + /* Frontend access type might change during Series::read() to allow parameter modification. * Backend access type stays unchanged for the lifetime of a Series. */ if( paddings.size() > 1u && IOHandler()->m_backendAccess == Access::READ_WRITE ) throw std::runtime_error("Cannot write to a series with inconsistent iteration padding. " "Please specify '%0T' or open as read-only."); } -void SeriesInterface::readOneIterationFileBased( std::string const & filePath ) +void Series::readOneIterationFileBased( std::string const & filePath ) { auto & series = get(); @@ -946,7 +940,7 @@ void SeriesInterface::readOneIterationFileBased( std::string const & filePath ) } void -SeriesInterface::readGorVBased( bool do_init ) +Series::readGorVBased( bool do_init ) { auto & series = get(); Parameter< Operation::OPEN_FILE > fOpen; @@ -1087,7 +1081,7 @@ SeriesInterface::readGorVBased( bool do_init ) } void -SeriesInterface::readBase() +Series::readBase() { auto & series = get(); using DT = Datatype; @@ -1163,7 +1157,7 @@ SeriesInterface::readBase() } std::string -SeriesInterface::iterationFilename( uint64_t i ) +Series::iterationFilename( uint64_t i ) { auto & series = get(); if( series.m_overrideFilebasedFilename.has_value() ) @@ -1177,8 +1171,8 @@ SeriesInterface::iterationFilename( uint64_t i ) + series.m_filenamePostfix; } -SeriesInterface::iterations_iterator -SeriesInterface::indexOf( Iteration const & iteration ) +Series::iterations_iterator +Series::indexOf( Iteration const & iteration ) { auto & series = get(); for( auto it = series.iterations.begin(); it != series.iterations.end(); @@ -1194,7 +1188,7 @@ SeriesInterface::indexOf( Iteration const & iteration ) } AdvanceStatus -SeriesInterface::advance( +Series::advance( AdvanceMode mode, internal::AttributableData & file, iterations_iterator begin, @@ -1291,7 +1285,7 @@ SeriesInterface::advance( } } - // We cannot call SeriesInterface::flush now, since the IO handler is still filled + // We cannot call Series::flush now, since the IO handler is still filled // from calling flush(Group|File)based, but has not been emptied yet // Do that manually IOHandler()->m_flushLevel = FlushLevel::UserFlush; @@ -1309,7 +1303,7 @@ SeriesInterface::advance( return *param.status; } -auto SeriesInterface::openIterationIfDirty( uint64_t index, Iteration iteration ) +auto Series::openIterationIfDirty( uint64_t index, Iteration iteration ) -> IterationOpened { /* @@ -1371,7 +1365,7 @@ auto SeriesInterface::openIterationIfDirty( uint64_t index, Iteration iteration return IterationOpened::RemainsClosed; } -void SeriesInterface::openIteration( uint64_t index, Iteration iteration ) +void Series::openIteration( uint64_t index, Iteration iteration ) { auto oldStatus = *iteration.m_closed; switch( *iteration.m_closed ) @@ -1463,47 +1457,13 @@ void parseJsonOptions( namespace internal { -#if openPMD_HAVE_MPI -SeriesInternal::SeriesInternal( - std::string const & filepath, - Access at, - MPI_Comm comm, - std::string const & options ) - : SeriesInterface{ - static_cast< internal::SeriesData * >( this ), - static_cast< internal::AttributableData * >( this ) } -{ - nlohmann::json optionsJson = auxiliary::parseOptions( options, comm ); - parseJsonOptions( *this, optionsJson ); - auto input = parseInput( filepath ); - auto handler = createIOHandler( - input->path, at, input->format, comm, std::move( optionsJson ) ); - init( handler, std::move( input ) ); -} -#endif - -SeriesInternal::SeriesInternal( - std::string const & filepath, Access at, std::string const & options ) - : SeriesInterface{ - static_cast< internal::SeriesData * >( this ), - static_cast< internal::AttributableData * >( this ) } -{ - nlohmann::json optionsJson = auxiliary::parseOptions( options ); - parseJsonOptions( *this, optionsJson ); - auto input = parseInput( filepath ); - auto handler = createIOHandler( - input->path, at, input->format, std::move( optionsJson ) ); - init( handler, std::move( input ) ); -} - -SeriesInternal::~SeriesInternal() +SeriesData::~SeriesData() { // we must not throw in a destructor try { - auto & series = get(); // WriteIterations gets the first shot at flushing - series.m_writeIterations = auxiliary::Option< WriteIterations >(); + this->m_writeIterations = auxiliary::Option< WriteIterations >(); /* * Scenario: A user calls `Series::flush()` but does not check for * thrown exceptions. The exception will propagate further up, usually @@ -1512,9 +1472,10 @@ SeriesInternal::~SeriesInternal() * needlessly flushed a second time. Otherwise, error messages can get * very confusing. */ - if( get().m_lastFlushSuccessful ) + if( this->m_lastFlushSuccessful ) { - flush(); + Series impl{ { this, []( auto const * ){} } }; + impl.flush(); } } catch( std::exception const & ex ) @@ -1528,17 +1489,15 @@ SeriesInternal::~SeriesInternal() } } // namespace internal -Series::Series( std::shared_ptr< internal::SeriesInternal > series_in ) - : SeriesInterface{ - series_in.get(), - static_cast< internal::AttributableData * >( series_in.get() ) } - , m_series{ std::move( series_in ) } - , iterations{ m_series->iterations } +Series::Series() : Attributable{ nullptr }, iterations{} { } -Series::Series() : SeriesInterface{ nullptr, nullptr }, iterations{} +Series::Series( std::shared_ptr< internal::SeriesData > data ) + : Attributable{ data } + , m_series{ std::move( data ) } { + iterations = m_series->iterations; } #if openPMD_HAVE_MPI @@ -1547,29 +1506,33 @@ Series::Series( Access at, MPI_Comm comm, std::string const & options ) - : SeriesInterface{ nullptr, nullptr } - , m_series{ std::make_shared< internal::SeriesInternal >( - filepath, at, comm, options ) } - , iterations{ m_series->iterations } -{ - AttributableInterface::m_attri = - static_cast< internal::AttributableData * >( m_series.get() ); - SeriesInterface::m_series = m_series.get(); + : Attributable{ nullptr } + , m_series{ new internal::SeriesData } +{ + Attributable::setData( m_series ); + iterations = m_series->iterations; + nlohmann::json optionsJson = auxiliary::parseOptions( options, comm ); + parseJsonOptions( get(), optionsJson ); + auto input = parseInput( filepath ); + auto handler = createIOHandler( + input->path, at, input->format, comm, std::move( optionsJson ) ); + init( handler, std::move( input ) ); } #endif Series::Series( - std::string const & filepath, - Access at, - std::string const & options) - : SeriesInterface{ nullptr, nullptr } - , m_series{ std::make_shared< internal::SeriesInternal >( - filepath, at, options ) } - , iterations{ m_series->iterations } -{ - AttributableInterface::m_attri = - static_cast< internal::AttributableData * >( m_series.get() ); - SeriesInterface::m_series = m_series.get(); + std::string const & filepath, Access at, std::string const & options ) + : Attributable{ nullptr } + , m_series{ new internal::SeriesData } +{ + Attributable::setData( m_series ); + iterations = m_series->iterations; + nlohmann::json optionsJson = auxiliary::parseOptions( options ); + parseJsonOptions( get(), optionsJson ); + auto input = parseInput( filepath ); + auto handler = createIOHandler( + input->path, at, input->format, std::move( optionsJson ) ); + init( handler, std::move( input ) ); } Series::operator bool() const diff --git a/src/backend/Attributable.cpp b/src/backend/Attributable.cpp index 82a6e42d63..043a6922a2 100644 --- a/src/backend/Attributable.cpp +++ b/src/backend/Attributable.cpp @@ -38,13 +38,16 @@ AttributableData::AttributableData() : m_writable{ this } } } -AttributableInterface::AttributableInterface( internal::AttributableData * attri ) +Attributable::Attributable() = default; + +Attributable::Attributable( + std::shared_ptr< internal::AttributableData > attri ) + : m_attri{ std::move( attri ) } { - setData( attri ); } Attribute -AttributableInterface::getAttribute(std::string const& key) const +Attributable::getAttribute(std::string const& key) const { auto & attri = get(); auto it = attri.m_attributes.find(key); @@ -55,7 +58,7 @@ AttributableInterface::getAttribute(std::string const& key) const } bool -AttributableInterface::deleteAttribute(std::string const& key) +Attributable::deleteAttribute(std::string const& key) { auto & attri = get(); if(Access::READ_ONLY == IOHandler()->m_frontendAccess ) @@ -75,7 +78,7 @@ AttributableInterface::deleteAttribute(std::string const& key) } std::vector< std::string > -AttributableInterface::attributes() const +Attributable::attributes() const { auto & attri = get(); std::vector< std::string > ret; @@ -87,55 +90,50 @@ AttributableInterface::attributes() const } size_t -AttributableInterface::numAttributes() const +Attributable::numAttributes() const { return get().m_attributes.size(); } bool -AttributableInterface::containsAttribute(std::string const &key) const +Attributable::containsAttribute(std::string const &key) const { auto & attri = get(); return attri.m_attributes.find(key) != attri.m_attributes.end(); } std::string -AttributableInterface::comment() const +Attributable::comment() const { return getAttribute("comment").get< std::string >(); } -AttributableInterface& -AttributableInterface::setComment(std::string const& c) +Attributable& +Attributable::setComment(std::string const& c) { setAttribute("comment", c); return *this; } void -AttributableInterface::seriesFlush() +Attributable::seriesFlush() { writable().seriesFlush(); } -internal::SeriesInternal const & AttributableInterface::retrieveSeries() const +Series Attributable::retrieveSeries() const { Writable const * findSeries = &writable(); while( findSeries->parent ) { findSeries = findSeries->parent; } - return auxiliary::deref_dynamic_cast< internal::SeriesInternal >( + auto seriesData = &auxiliary::deref_dynamic_cast< internal::SeriesData >( findSeries->attributable ); + return Series{ { seriesData, []( auto const * ){} } }; } -internal::SeriesInternal & AttributableInterface::retrieveSeries() -{ - return const_cast< internal::SeriesInternal & >( - static_cast< AttributableInterface const * >( this )->retrieveSeries() ); -} - -Iteration const & AttributableInterface::containingIteration() const +Iteration const & Attributable::containingIteration() const { std::vector< Writable const * > searchQueue; searchQueue.reserve( 7 ); @@ -164,7 +162,7 @@ Iteration const & AttributableInterface::containingIteration() const * Since the class Iteration itself still follows the old class design, * we will have to take a detour via Series. */ - auto & series = auxiliary::deref_dynamic_cast< internal::SeriesInternal >( + auto & series = auxiliary::deref_dynamic_cast< internal::SeriesData >( ( *searchQueue.rbegin() )->attributable ); for( auto const & pair : series.iterations ) { @@ -177,10 +175,10 @@ Iteration const & AttributableInterface::containingIteration() const "Containing iteration not found in containing Series." ); } -Iteration & AttributableInterface::containingIteration() +Iteration & Attributable::containingIteration() { return const_cast< Iteration & >( - static_cast< AttributableInterface const * >( this ) + static_cast< Attributable const * >( this ) ->containingIteration() ); } @@ -189,7 +187,7 @@ std::string Attributable::MyPath::filePath() const return directory + seriesName + seriesExtension; } -auto AttributableInterface::myPath() const -> MyPath +auto Attributable::myPath() const -> MyPath { MyPath res; Writable const * findSeries = &writable(); @@ -209,23 +207,24 @@ auto AttributableInterface::myPath() const -> MyPath findSeries = findSeries->parent; } std::reverse(res.group.begin(), res.group.end() ); - auto const & series = - auxiliary::deref_dynamic_cast< internal::SeriesInternal >( + auto & seriesData = + auxiliary::deref_dynamic_cast< internal::SeriesData >( findSeries->attributable ); + Series series{ { &seriesData, []( auto const * ) {} } }; res.seriesName = series.name(); - res.seriesExtension = suffix( series.m_format ); + res.seriesExtension = suffix( seriesData.m_format ); res.directory = IOHandler()->directory; return res; } void -AttributableInterface::seriesFlush( FlushLevel level ) +Attributable::seriesFlush( FlushLevel level ) { writable().seriesFlush( level ); } void -AttributableInterface::flushAttributes() +Attributable::flushAttributes() { if( IOHandler()->m_flushLevel == FlushLevel::SkeletonOnly ) { @@ -247,7 +246,7 @@ AttributableInterface::flushAttributes() } void -AttributableInterface::readAttributes( ReadMode mode ) +Attributable::readAttributes( ReadMode mode ) { auto & attri = get(); Parameter< Operation::LIST_ATTS > aList; @@ -444,7 +443,7 @@ AttributableInterface::readAttributes( ReadMode mode ) } void -AttributableInterface::linkHierarchy(Writable& w) +Attributable::linkHierarchy(Writable& w) { auto handler = w.IOHandler; writable().IOHandler = handler; diff --git a/src/backend/BaseRecordComponent.cpp b/src/backend/BaseRecordComponent.cpp index de9174a329..54503f8313 100644 --- a/src/backend/BaseRecordComponent.cpp +++ b/src/backend/BaseRecordComponent.cpp @@ -70,13 +70,13 @@ BaseRecordComponent::availableChunks() BaseRecordComponent::BaseRecordComponent( std::shared_ptr< internal::BaseRecordComponentData > data) - : AttributableInterface{ data.get() } + : Attributable{ data } , m_baseRecordComponentData{ std::move( data ) } {} BaseRecordComponent::BaseRecordComponent() - : AttributableInterface{ nullptr } + : Attributable{ nullptr } { - AttributableInterface::setData( m_baseRecordComponentData.get() ); + Attributable::setData( m_baseRecordComponentData ); } } // namespace openPMD diff --git a/src/backend/Writable.cpp b/src/backend/Writable.cpp index a22a18bf52..606332cd05 100644 --- a/src/backend/Writable.cpp +++ b/src/backend/Writable.cpp @@ -43,7 +43,8 @@ namespace openPMD void Writable::seriesFlush( FlushLevel level ) { - auto & series = AttributableInterface( attributable ).retrieveSeries(); + auto series = Attributable( { attributable, []( auto const * ){} } ) + .retrieveSeries(); series.flush_impl( series.iterations.begin(), series.iterations.end(), level ); } diff --git a/test/AuxiliaryTest.cpp b/test/AuxiliaryTest.cpp index f296913c20..aaadcc9bd0 100644 --- a/test/AuxiliaryTest.cpp +++ b/test/AuxiliaryTest.cpp @@ -32,7 +32,7 @@ namespace openPMD { namespace test { -struct TestHelper : public LegacyAttributable +struct TestHelper : public Attributable { TestHelper() { @@ -302,7 +302,7 @@ TEST_CASE( "container_access_test", "[auxiliary]" ) TEST_CASE( "attributable_default_test", "[auxiliary]" ) { - LegacyAttributable a; + Attributable a; REQUIRE(a.numAttributes() == 0); } From c497ea334177c1292ecd6aead9c34059ebe3b4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 30 Sep 2021 12:05:05 +0200 Subject: [PATCH 5/7] Apply redesign to Iteration class --- include/openPMD/Iteration.hpp | 155 +++++++++++++++++++--------------- src/Iteration.cpp | 39 +++++---- src/ReadIterations.cpp | 6 +- src/Series.cpp | 75 ++++++++-------- src/backend/Attributable.cpp | 2 +- 5 files changed, 150 insertions(+), 127 deletions(-) diff --git a/include/openPMD/Iteration.hpp b/include/openPMD/Iteration.hpp index b3b99138b6..d82ae8f572 100644 --- a/include/openPMD/Iteration.hpp +++ b/include/openPMD/Iteration.hpp @@ -32,11 +32,78 @@ namespace openPMD { +namespace internal +{ + /** + * @brief Whether an iteration has been closed yet. + * + */ + enum class CloseStatus + { + ParseAccessDeferred, //!< The reader has not yet parsed this iteration + Open, //!< Iteration has not been closed + ClosedInFrontend, /*!< Iteration has been closed, but task has not yet + been propagated to the backend */ + ClosedInBackend, /*!< Iteration has been closed and task has been + propagated to the backend */ + ClosedTemporarily /*!< Iteration has been closed internally and may + be reopened later */ + }; + + struct DeferredParseAccess + { + /** + * The group path within /data containing this iteration. + * Example: "1" for iteration 1, "" in variable-based iteration + * encoding. + */ + std::string path; + /** + * The iteration index as accessed by the user in series.iterations[i] + */ + uint64_t iteration = 0; + /** + * If this iteration is part of a Series with file-based layout. + * (Group- and variable-based parsing shares the same code logic.) + */ + bool fileBased = false; + /** + * If fileBased == true, the file name (without file path) of the file + * containing this iteration. + */ + std::string filename; + }; + + class IterationData : public AttributableData + { + public: + /* + * An iteration may be logically closed in the frontend, + * but not necessarily yet in the backend. + * Will be propagated to the backend upon next flush. + * Store the current status. + * Once an iteration has been closed, no further flushes shall be performed. + * If flushing a closed file, the old file may otherwise be overwritten. + */ + CloseStatus m_closed = CloseStatus::Open; + + /** + * 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. + */ + StepStatus m_stepStatus = StepStatus::NoStep; + + auxiliary::Option< DeferredParseAccess > m_deferredParseAccess{}; + }; +} /** @brief Logical compilation of data from one snapshot (e.g. a single simulation cycle). * * @see https://github.com/openPMD/openPMD-standard/blob/latest/STANDARD.md#required-attributes-for-the-basepath */ -class Iteration : public LegacyAttributable +class Iteration : public Attributable { template< typename T, @@ -151,42 +218,32 @@ class Iteration : public LegacyAttributable bool closedByWriter() const; - Container< Mesh > meshes; - Container< ParticleSpecies > particles; //particleSpecies? + Container< Mesh > meshes{}; + Container< ParticleSpecies > particles{}; //particleSpecies? virtual ~Iteration() = default; private: Iteration(); - struct DeferredParseAccess + std::shared_ptr< internal::IterationData > m_iterationData{ + new internal::IterationData }; + + inline internal::IterationData const & get() const { - /** - * The group path within /data containing this iteration. - * Example: "1" for iteration 1, "" in variable-based iteration - * encoding. - */ - std::string path; - /** - * The iteration index as accessed by the user in series.iterations[i] - */ - uint64_t iteration = 0; - /** - * If this iteration is part of a Series with file-based layout. - * (Group- and variable-based parsing shares the same code logic.) - */ - bool fileBased = false; - /** - * If fileBased == true, the file name (without file path) of the file - * containing this iteration. - */ - std::string filename; - }; + return *m_iterationData; + } + + inline internal::IterationData & get() + { + return const_cast< internal::IterationData & >( + static_cast< Iteration const * >( this )->get() ); + } void flushFileBased(std::string const&, uint64_t); void flushGroupBased(uint64_t); void flushVariableBased(uint64_t); void flush(); - void deferParseAccess( DeferredParseAccess ); + void deferParseAccess( internal::DeferredParseAccess ); /* * Control flow for read(), readFileBased(), readGroupBased() and * read_impl(): @@ -199,7 +256,7 @@ class Iteration : public LegacyAttributable * allow for those different control flows. * Finally, read_impl() is called which contains the common parsing * logic for an iteration. - * + * * reread() reads again an Iteration that has been previously read. * Calling it on an Iteration not yet parsed is an error. * @@ -210,47 +267,7 @@ class Iteration : public LegacyAttributable void readGorVBased( std::string const & groupPath ); void read_impl( std::string const & groupPath ); - /** - * @brief Whether an iteration has been closed yet. - * - */ - enum class CloseStatus - { - ParseAccessDeferred, //!< The reader has not yet parsed this iteration - Open, //!< Iteration has not been closed - ClosedInFrontend, /*!< Iteration has been closed, but task has not yet - been propagated to the backend */ - ClosedInBackend, /*!< Iteration has been closed and task has been - propagated to the backend */ - ClosedTemporarily /*!< Iteration has been closed internally and may - be reopened later */ - }; - - /* - * An iteration may be logically closed in the frontend, - * but not necessarily yet in the backend. - * Will be propagated to the backend upon next flush. - * Store the current status. - * Once an iteration has been closed, no further flushes shall be performed. - * If flushing a closed file, the old file may otherwise be overwritten. - */ - std::shared_ptr< CloseStatus > m_closed = - std::make_shared< CloseStatus >( CloseStatus::Open ); - - /** - * 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. - */ - std::shared_ptr< StepStatus > m_stepStatus = - std::make_shared< StepStatus >( StepStatus::NoStep ); - std::shared_ptr< auxiliary::Option< DeferredParseAccess > > - m_deferredParseAccess = - std::make_shared< auxiliary::Option< DeferredParseAccess > >( - auxiliary::Option< DeferredParseAccess >() ); /** * @brief Begin an IO step on the IO file (or file-like object) @@ -306,7 +323,7 @@ class Iteration : public LegacyAttributable /** * @brief Link with parent. - * + * * @param w The Writable representing the parent. */ virtual void linkHierarchy(Writable& w); @@ -314,7 +331,7 @@ class Iteration : public LegacyAttributable /** * @brief Access an iteration in read mode that has potentially not been * parsed yet. - * + * */ void runDeferredParseAccess(); }; // Iteration diff --git a/src/Iteration.cpp b/src/Iteration.cpp index 4e4290349c..cb5b2fb709 100644 --- a/src/Iteration.cpp +++ b/src/Iteration.cpp @@ -33,10 +33,12 @@ namespace openPMD { -Iteration::Iteration() - : meshes{Container< Mesh >()}, - particles{Container< ParticleSpecies >()} +using internal::CloseStatus; +using internal::DeferredParseAccess; + +Iteration::Iteration() : Attributable{ nullptr } { + Attributable::setData( m_iterationData ); setTime(static_cast< double >(0)); setDt(static_cast< double >(1)); setTimeUnitSI(1); @@ -82,25 +84,26 @@ using iterator_t = Container< Iteration, uint64_t >::iterator; Iteration & Iteration::close( bool _flush ) { + auto & it = get(); StepStatus flag = getStepStatus(); // update close status - switch( *m_closed ) + switch( it.m_closed ) { case CloseStatus::Open: case CloseStatus::ClosedInFrontend: - *m_closed = CloseStatus::ClosedInFrontend; + it.m_closed = CloseStatus::ClosedInFrontend; break; case CloseStatus::ClosedTemporarily: // should we bother to reopen? if( dirtyRecursive() ) { // let's reopen - *m_closed = CloseStatus::ClosedInFrontend; + it.m_closed = CloseStatus::ClosedInFrontend; } else { // don't reopen - *m_closed = CloseStatus::ClosedInBackend; + it.m_closed = CloseStatus::ClosedInBackend; } break; case CloseStatus::ParseAccessDeferred: @@ -143,9 +146,10 @@ Iteration::close( bool _flush ) Iteration & Iteration::open() { - if( *m_closed == CloseStatus::ParseAccessDeferred ) + auto & it = get(); + if( it.m_closed == CloseStatus::ParseAccessDeferred ) { - *m_closed = CloseStatus::Open; + it.m_closed = CloseStatus::Open; } runDeferredParseAccess(); Series s = retrieveSeries(); @@ -159,7 +163,7 @@ Iteration::open() bool Iteration::closed() const { - switch( *m_closed ) + switch( get().m_closed ) { case CloseStatus::ParseAccessDeferred: case CloseStatus::Open: @@ -318,17 +322,18 @@ Iteration::flush() void Iteration::deferParseAccess( DeferredParseAccess dr ) { - *m_deferredParseAccess = + get().m_deferredParseAccess = auxiliary::makeOption< DeferredParseAccess >( std::move( dr ) ); } void Iteration::read() { - if( !m_deferredParseAccess->has_value() ) + auto & it = get(); + if( !it.m_deferredParseAccess.has_value() ) { return; } - auto const & deferred = m_deferredParseAccess->get(); + auto const & deferred = it.m_deferredParseAccess.get(); if( deferred.fileBased ) { readFileBased( deferred.filename, deferred.path ); @@ -338,12 +343,12 @@ void Iteration::read() readGorVBased( deferred.path ); } // reset this thing - *m_deferredParseAccess = auxiliary::Option< DeferredParseAccess >(); + it.m_deferredParseAccess = auxiliary::Option< DeferredParseAccess >(); } void Iteration::reread( std::string const & path ) { - if( m_deferredParseAccess->has_value() ) + if( get().m_deferredParseAccess.has_value() ) { throw std::runtime_error( "[Iteration] Internal control flow error: Trying to reread an " @@ -609,7 +614,7 @@ Iteration::getStepStatus() { using IE = IterationEncoding; case IE::fileBased: - return *this->m_stepStatus; + return get().m_stepStatus; case IE::groupBased: case IE::variableBased: return s.get().m_stepStatus; @@ -626,7 +631,7 @@ Iteration::setStepStatus( StepStatus status ) { using IE = IterationEncoding; case IE::fileBased: - *this->m_stepStatus = status; + get().m_stepStatus = status; break; case IE::groupBased: case IE::variableBased: diff --git a/src/ReadIterations.cpp b/src/ReadIterations.cpp index 14329abf5d..6c88ccd2be 100644 --- a/src/ReadIterations.cpp +++ b/src/ReadIterations.cpp @@ -49,8 +49,8 @@ SeriesIterator::SeriesIterator( Series series ) * Use case: See Python ApiTest testListSeries: * Call listSeries twice. */ - if( *it->second.m_closed != - Iteration::CloseStatus::ClosedInBackend ) + if( it->second.get().m_closed != + internal::CloseStatus::ClosedInBackend ) { it->second.open(); } @@ -136,7 +136,7 @@ SeriesIterator & SeriesIterator::operator++() return *this; } m_currentIteration = it->first; - if( *it->second.m_closed != Iteration::CloseStatus::ClosedInBackend ) + if( it->second.get().m_closed != internal::CloseStatus::ClosedInBackend ) { it->second.open(); } diff --git a/src/Series.cpp b/src/Series.cpp index abc1f8a797..a8c18e70ef 100644 --- a/src/Series.cpp +++ b/src/Series.cpp @@ -606,13 +606,13 @@ Series::flushFileBased( iterations_iterator begin, iterations_iterator end ) } // Phase 2 - if( *it->second.m_closed == - Iteration::CloseStatus::ClosedInFrontend ) + if( it->second.get().m_closed == + internal::CloseStatus::ClosedInFrontend ) { Parameter< Operation::CLOSE_FILE > fClose; IOHandler()->enqueue( IOTask( &it->second, std::move( fClose ) ) ); - *it->second.m_closed = Iteration::CloseStatus::ClosedInBackend; + it->second.get().m_closed = internal::CloseStatus::ClosedInBackend; } // Phase 3 @@ -650,13 +650,13 @@ Series::flushFileBased( iterations_iterator begin, iterations_iterator end ) } // Phase 2 - if( *it->second.m_closed == - Iteration::CloseStatus::ClosedInFrontend ) + if( it->second.get().m_closed == + internal::CloseStatus::ClosedInFrontend ) { Parameter< Operation::CLOSE_FILE > fClose; IOHandler()->enqueue( IOTask( &it->second, std::move( fClose ) ) ); - *it->second.m_closed = Iteration::CloseStatus::ClosedInBackend; + it->second.get().m_closed = internal::CloseStatus::ClosedInBackend; } // Phase 3 @@ -689,11 +689,11 @@ Series::flushGorVBased( iterations_iterator begin, iterations_iterator end ) } // Phase 2 - if( *it->second.m_closed == - Iteration::CloseStatus::ClosedInFrontend ) + if( it->second.get().m_closed == + internal::CloseStatus::ClosedInFrontend ) { // the iteration has no dedicated file in group-based mode - *it->second.m_closed = Iteration::CloseStatus::ClosedInBackend; + it->second.get().m_closed = internal::CloseStatus::ClosedInBackend; } // Phase 3 @@ -742,11 +742,11 @@ Series::flushGorVBased( iterations_iterator begin, iterations_iterator end ) } // Phase 2 - if( *it->second.m_closed == - Iteration::CloseStatus::ClosedInFrontend ) + if( it->second.get().m_closed == + internal::CloseStatus::ClosedInFrontend ) { // the iteration has no dedicated file in group-based mode - *it->second.m_closed = Iteration::CloseStatus::ClosedInBackend; + it->second.get().m_closed = internal::CloseStatus::ClosedInBackend; } } @@ -827,14 +827,14 @@ Series::readFileBased( ) Parameter< Operation::CLOSE_FILE > fClose; iteration.IOHandler()->enqueue( IOTask( &iteration, fClose ) ); iteration.IOHandler()->flush(); - *iteration.m_closed = Iteration::CloseStatus::ClosedTemporarily; + iteration.get().m_closed = internal::CloseStatus::ClosedTemporarily; }; if( series.m_parseLazily ) { for( auto & iteration : series.iterations ) { - *iteration.second.m_closed = - Iteration::CloseStatus::ParseAccessDeferred; + iteration.second.get().m_closed = + internal::CloseStatus::ParseAccessDeferred; } // open the last iteration, just to parse Series attributes auto getLastIteration = series.iterations.end(); @@ -1028,7 +1028,7 @@ Series::readGorVBased( bool do_init ) { return; } - if( *i.m_closed != Iteration::CloseStatus::ParseAccessDeferred ) + if( i.get().m_closed != internal::CloseStatus::ParseAccessDeferred ) { pOpen.path = path; IOHandler()->enqueue( IOTask( &i, pOpen ) ); @@ -1043,11 +1043,11 @@ Series::readGorVBased( bool do_init ) if( !series.m_parseLazily ) { i.runDeferredParseAccess(); - *i.m_closed = Iteration::CloseStatus::Open; + i.get().m_closed = internal::CloseStatus::Open; } else { - *i.m_closed = Iteration::CloseStatus::ParseAccessDeferred; + i.get().m_closed = internal::CloseStatus::ParseAccessDeferred; } } }; @@ -1205,23 +1205,24 @@ Series::advance( * In order to avoid having those tasks automatically appended by * flush_impl(), set CloseStatus to Open for now. */ - Iteration::CloseStatus oldCloseStatus = *iteration.m_closed; - if( oldCloseStatus == Iteration::CloseStatus::ClosedInFrontend ) + auto & itData = iteration.get(); + internal::CloseStatus oldCloseStatus = itData.m_closed; + if( oldCloseStatus == internal::CloseStatus::ClosedInFrontend ) { - *iteration.m_closed = Iteration::CloseStatus::Open; + itData.m_closed = internal::CloseStatus::Open; } flush_impl( begin, end, FlushLevel::UserFlush, /* flushIOHandler = */ false ); - if( oldCloseStatus == Iteration::CloseStatus::ClosedInFrontend ) + if( oldCloseStatus == internal::CloseStatus::ClosedInFrontend ) { // Series::flush() would normally turn a `ClosedInFrontend` into // a `ClosedInBackend`. Do that manually. - *iteration.m_closed = Iteration::CloseStatus::ClosedInBackend; + itData.m_closed = internal::CloseStatus::ClosedInBackend; } else if( - oldCloseStatus == Iteration::CloseStatus::ClosedInBackend && + oldCloseStatus == internal::CloseStatus::ClosedInBackend && series.m_iterationEncoding == IterationEncoding::fileBased ) { /* @@ -1233,7 +1234,7 @@ Series::advance( } Parameter< Operation::ADVANCE > param; - if( *iteration.m_closed == Iteration::CloseStatus::ClosedTemporarily && + if( itData.m_closed == internal::CloseStatus::ClosedTemporarily && series.m_iterationEncoding == IterationEncoding::fileBased ) { /* @@ -1251,7 +1252,7 @@ Series::advance( } - if( oldCloseStatus == Iteration::CloseStatus::ClosedInFrontend && + if( oldCloseStatus == internal::CloseStatus::ClosedInFrontend && mode == AdvanceMode::ENDSTEP ) { using IE = IterationEncoding; @@ -1259,14 +1260,14 @@ Series::advance( { case IE::fileBased: { - if( *iteration.m_closed != - Iteration::CloseStatus::ClosedTemporarily ) + if( itData.m_closed != + internal::CloseStatus::ClosedTemporarily ) { Parameter< Operation::CLOSE_FILE > fClose; IOHandler()->enqueue( IOTask( &iteration, std::move( fClose ) ) ); } - *iteration.m_closed = Iteration::CloseStatus::ClosedInBackend; + itData.m_closed = internal::CloseStatus::ClosedInBackend; break; } case IE::groupBased: @@ -1277,7 +1278,7 @@ Series::advance( // In group-based iteration layout, files are // not closed on a per-iteration basis // We will treat it as such nonetheless - *iteration.m_closed = Iteration::CloseStatus::ClosedInBackend; + itData.m_closed = internal::CloseStatus::ClosedInBackend; break; } case IE::variableBased: // no action necessary @@ -1310,12 +1311,12 @@ auto Series::openIterationIfDirty( uint64_t index, Iteration iteration ) * Check side conditions on accessing iterations, and if they are fulfilled, * forward function params to openIteration(). */ - if( *iteration.m_closed == Iteration::CloseStatus::ParseAccessDeferred ) + if( iteration.get().m_closed == internal::CloseStatus::ParseAccessDeferred ) { return IterationOpened::RemainsClosed; } bool const dirtyRecursive = iteration.dirtyRecursive(); - if( *iteration.m_closed == Iteration::CloseStatus::ClosedInBackend ) + if( iteration.get().m_closed == internal::CloseStatus::ClosedInBackend ) { // file corresponding with the iteration has previously been // closed and fully flushed @@ -1367,10 +1368,10 @@ auto Series::openIterationIfDirty( uint64_t index, Iteration iteration ) void Series::openIteration( uint64_t index, Iteration iteration ) { - auto oldStatus = *iteration.m_closed; - switch( *iteration.m_closed ) + auto oldStatus = iteration.get().m_closed; + switch( oldStatus ) { - using CL = Iteration::CloseStatus; + using CL = internal::CloseStatus; case CL::ClosedInBackend: throw std::runtime_error( "[Series] Detected illegal access to iteration that " @@ -1378,7 +1379,7 @@ void Series::openIteration( uint64_t index, Iteration iteration ) case CL::ParseAccessDeferred: case CL::Open: case CL::ClosedTemporarily: - *iteration.m_closed = CL::Open; + iteration.get().m_closed = CL::Open; break; case CL::ClosedInFrontend: // just keep it like it is @@ -1406,7 +1407,7 @@ void Series::openIteration( uint64_t index, Iteration iteration ) */ if( !iteration.written() && ( IOHandler()->m_frontendAccess == Access::CREATE || - oldStatus != Iteration::CloseStatus::ParseAccessDeferred ) ) + oldStatus != internal::CloseStatus::ParseAccessDeferred ) ) { // nothing to do, file will be opened by writing routines break; diff --git a/src/backend/Attributable.cpp b/src/backend/Attributable.cpp index 043a6922a2..d1e5a221ef 100644 --- a/src/backend/Attributable.cpp +++ b/src/backend/Attributable.cpp @@ -166,7 +166,7 @@ Iteration const & Attributable::containingIteration() const ( *searchQueue.rbegin() )->attributable ); for( auto const & pair : series.iterations ) { - if( &pair.second.get() == attr ) + if( &static_cast< Attributable const & >( pair.second ).get() == attr ) { return pair.second; } From 291deeda52d27631059ec65c5cc6d5747b2c4a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Thu, 30 Sep 2021 12:06:27 +0200 Subject: [PATCH 6/7] Remove LegacyAttributable --- include/openPMD/backend/Attributable.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/openPMD/backend/Attributable.hpp b/include/openPMD/backend/Attributable.hpp index 1198dc9496..ded6aabd37 100644 --- a/include/openPMD/backend/Attributable.hpp +++ b/include/openPMD/backend/Attributable.hpp @@ -380,8 +380,6 @@ class Attributable virtual void linkHierarchy(Writable& w); }; // Attributable -using LegacyAttributable = Attributable; - //TODO explicitly instantiate Attributable::setAttribute for all T in Datatype template< typename T > inline bool From e23792a45765ad732e7b2808711e3ad43eb90f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Fri, 1 Oct 2021 16:08:53 +0200 Subject: [PATCH 7/7] Update documentation --- docs/source/dev/design.rst | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/docs/source/dev/design.rst b/docs/source/dev/design.rst index a3ce26de36..547bb59e38 100644 --- a/docs/source/dev/design.rst +++ b/docs/source/dev/design.rst @@ -110,33 +110,23 @@ all meta-data access stores in the ``Attributable`` part of an object and follow (future work: use `CRTP `_) openPMD frontend classes are designed as handles that user code may copy an arbitrary amount of times, all copies sharing common resources. -In an undergoing effort, we are moving to a PIMPL-based pattern to make this design more explicit. +The internal shared state of such handles is stored as a single ``shared_ptr``. This serves to separate data from implementation, demonstrated by the class hierarchy of ``Series``: * ``SeriesData`` contains all actual data members of ``Series``, representing the resources shared between instances. Its copy/move constructors/assignment operators are deleted, thus pinning it at one memory location. It has no implementation. -* ``SeriesInterface`` defines the interface of ``Series``. - Its only data member is a pointer to its associated instance of ``SeriesData``. - Its purpose is to serve as a parent class for any class that implements the ``Series`` interface. - The implementing class should deal with resource management for the instance of ``SeriesData``, ``SeriesInterface`` itself performs no resource management for the data and will assume that the pointer points at a valid instance. - (This PIMPL-based design allows us to avoid a similar template-heavy design based on CRTP.) -* ``SeriesInternal`` is the class created by inheriting directly from ``SeriesData`` and ``SeriesInterface``. - It is intended for internal usage, pinned at a memory location just like ``SeriesData``, but carrying an implementation. - Its constructor and destructor define the setup and tear-down of shared resources for ``Series``. -* ``Series`` is a wrapper around a shared pointer to ``SeriesInternal`` and also derives from ``SeriesInterface``. - It implements handle semantics, serving as interface to users. - -Other classes within our object model of the openPMD hierarchy are planned to follow in this design. +* ``Series`` defines the interface of the class and is used by client code. + Its only data member is a shared pointer to its associated instance of ``SeriesData``. + (This pointer-based design allows us to avoid a similar template-heavy design based on CRTP.) +* A non-owning instance of the ``Series`` class can be constructed on the fly from a ``SeriesData`` object by passing a ``shared_ptr`` with no-op destructor when constructing the ``Series``. + Care must be taken to not expose such an object to users as the type system does not distinguish between an owning and a non-owning ``Series`` object. +* Frontend classes that define the openPMD group hierarchy follow this same design (except for some few that do not define data members). + These classes all inherit from ``Attributable``. + Their shared state inherits from ``AttributableData``. + A downside of this design is that the ``shared_ptr`` pointing to the internal state is redundantly stored for each level in the class hierarchy, ``static_cast``-ed to the corresponding level. + All these pointers reference the same internal object. The class hierarchy of ``Attributable`` follows a similar design, with some differences due to its nature as a mixin class that is not instantiated directly: -* ``AttributableData`` serves the same purpose as ``SeriesData``. -* ``AttributableInterface`` serves the same purpose as ``SeriesData``. -* Equivalents to ``SeriesInternal`` and ``Series`` do not exist since a combination of ``AttributableData`` and ``AttributableInterface`` is added as a mixin to other classes. - As a common base class exposed to user code, ``AttributableInterface`` is aliased as ``Attributable``. -* For classes not yet following the PIMPL-based design, ``LegacyAttributable`` wraps around a shared pointer to ``AttributableData`` and derives from ``AttributableInterface``. - The ``Attributable`` mixin can be added to those classes by deriving from ``LegacyAttributable``. -* The ``Attributable`` mixin is added to ``Series`` by deriving ``SeriesData`` from ``AttributableData`` and ``SeriesInterface`` from ``AttributableInterface``. - The ``Series`` class is the entry point to the openPMD-api. An instance of this class always represents an entire series (of iterations), regardless of how this series is modeled in storage or transport (e.g. as multiple files, as a stream, as variables containing multiple snapshots of a dataset or as one file containing all iterations at once).