Split Series into an internal and an external class#886
Conversation
329f678 to
942762d
Compare
e1a9e9f to
15f9a86
Compare
f78a2c7 to
d419923
Compare
7866418 to
c68def1
Compare
| namespace internal | ||
| { | ||
| /** | ||
| * @brief Data members for Series. Pinned at one memory location. |
There was a problem hiding this comment.
Note: maybe it's good to describe that Impl and Data are mixing classes and that ...Internal is used as the fixed in-memory storage for each interface class.
There was a problem hiding this comment.
I have added that description to design.rst.
b84c80f to
23685a5
Compare
Fix invasive tests
Make AttributableImpl destructor virtual Rename Attributable -> LegacyAttributable Use Attributable::retrieveSeries for Writable::flushSeries
Make BASEPATH a member of SeriesImpl Documentation for Series
This is stolen from PR openPMD#804. This PR fixes the issues from that one, so those tests are passing now.
ax3l
left a comment
There was a problem hiding this comment.
This looks really good, thank you for spearheading this!
A left a small question and we should definitely document the new structure, which can also go with further PRs that refactor other classes.
Do you plan a follow-up that removes LegacyAttributable?
| using iterations_iterator = iterations_t::iterator; | ||
|
|
||
| Container< Iteration, uint64_t > iterations; | ||
| internal::SeriesData * m_series; |
There was a problem hiding this comment.
I wonder if at this point, is a raw pointer the best pattern?
There was a problem hiding this comment.
The …Impl classes are pure interface classes, with the explicit goal of allowing us to be generic over resource management. The reason why we need the Impl classes is because we want a unified implementation for classes that manage their resources in different ways:
- As part of the same object, pinned in one memory location, as in
SeriesInternal. - Managed as a shared resource with a
shared_ptr, as inSeries.
So, the concrete approach at resource management depends on the class deriving from SeriesImpl – SeriesImpl must support any approach. Two ways to do this:
- Use templates to specifiy the kind of resource management to be used by
SeriesImpl. That's what I had implemented previously. Avoids using pointers, at the cost of introducing templates, nearly doubling compile times in my tests. - Relieve
SeriesImploff the task of resource management alltogether and just use a pointer. It's the task of the deriving class to deal with resource management and ensure via RAII mechanisms that this is done in a safe manner.
| * Access via stepStatus() method to automatically select the correct | ||
| * one among both flags. | ||
| */ | ||
| std::shared_ptr< StepStatus > |
There was a problem hiding this comment.
Memo to ourselves: as mentioned in the PR description, those shared_ptr members will become regular members
23685a5 to
70f0020
Compare
5d741f6 to
7447014
Compare
As explained in the documentation now, |
For comparing: franzpoeschel/openPMD-api@47d504d...franzpoeschel:topic-internal-seriesThis is a
suggestion to addressfix for the issues with our current C++ object model of the openPMD hierarchy, observed in #534, #804 and #814.The interface is now feature-complete, existing applications using the openPMD API should now work as before.
(Exception: The classAttributableexists no longer and is now a templateAttributableImpl<T>.)The major downside of the approach that I am proposing is that it is a very heavy change, moving large chunks of code to different places. Updating this to the currentdevwill likely be a project of its own.I've tried to avoid breaking our API, but given that this is a heavy change to our frontend classes, I still consider this API breaking.
Idea: Split
Seriesinto several classes:1. An internal class
internal::SeriesDatathat holds the actual data members, but has no logic otherwise:This class cannot be copied or moved, making it safe to reference by pointer. The thousands of
shared_ptrmembers can be replaced by plain data members in a subsequent step.2. A class
SeriesImplthat has no data members, but holds only the implementation and interface ofSeriesand a single pointer toSeriesData. This pattern works only becauseSeriesDatais not moveable and the pointer to the instance will hence not change:These can then be combined to define an internal and an external
Seriesclass:3. Internal Series:
This class is not copyable and movable since it derives directly from
SeriesData. It is merely aSeriesDatawith implementation.4. An external class
Serieswith (nearly) the same interface as the currentSeriesthat can be safely copied.Note that the actual data members are hidden behind the
shared_ptr<SeriesInternal> m_series. The destructor ofSeriesInternalwill only run once all copies ofSeriesare gone.The classes
ReadIterationsandWriteIterationscan now safely hold copies of theSeries, pointers are no longer necessary. (This is the reason why those methods are part ofSeriesand not ofSeriesImpl)Note A similar split was necessary to do for
Attributable. I have left those parts out in the code snippets above. We want the interface ofAttributableavailable (1) forSeriesInternal(2) as well as forSeries. The data members should again be present only for the internal class.The template pattern that I have used for this corresponds to interfaces/traits in other languages (e.g.
implement SeriesImpl for SeriesInternal) and is easily composable, so this was no issue.TODO
SeriesWrapperas a type parameter toSeriesImpl, we could instead just directly pass a pointer toSeriesData. This would heavily reduce the amount of templates (and hence compile time), and the new design ensures that passing a pointer is safe.