-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
?
|
||
Container< Iteration, uint64_t > iterations; | ||
internal::SeriesData * m_series; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if at this point, is a raw pointer the best pattern?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
SeriesImpl
off 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hurray! ✨
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 classAttributable
exists 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 currentdev
will 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
Series
into several classes:1. An internal class
internal::SeriesData
that 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_ptr
members can be replaced by plain data members in a subsequent step.2. A class
SeriesImpl
that has no data members, but holds only the implementation and interface ofSeries
and a single pointer toSeriesData
. This pattern works only becauseSeriesData
is not moveable and the pointer to the instance will hence not change:These can then be combined to define an internal and an external
Series
class:3. Internal Series:
This class is not copyable and movable since it derives directly from
SeriesData
. It is merely aSeriesData
with implementation.4. An external class
Series
with (nearly) the same interface as the currentSeries
that can be safely copied.Note that the actual data members are hidden behind the
shared_ptr<SeriesInternal> m_series
. The destructor ofSeriesInternal
will only run once all copies ofSeries
are gone.The classes
ReadIterations
andWriteIterations
can now safely hold copies of theSeries
, pointers are no longer necessary. (This is the reason why those methods are part ofSeries
and 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 ofAttributable
available (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
SeriesWrapper
as 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.