Skip to content

Commit 695e82f

Browse files
committed
Reading changes: Use snapshot attribute
This means that the snapshot attribute, if present, is used for accessing iterations inside `series.readIterations()`. Fallback to the old behavior (linear progression through iterations) if the attribute is not found. Variable-b. encoding: Allow several (equivalent) iterations per step This means that a single step can be marked by /data/snapshot to represent iterations 0,10,20,30 at the same time. The underlying data is the same, but the API will treat it as 4 times a different iteration with equivalent content. Avoid const_cast by introducing a parsing state and use that when re-parsing. Skip repeated iterations that occur in Append mode Before the explicit iteration-step mapping, these were not seen by reading procedures at all. Now they are, so we skip the second instance. Better error message when calling readIterations() too late This commit includes some refactoring 1. Remove recursion of operator++(), this leads to constant memory usage rather than filling the stack at some point 2. Extract subroutines from operator++() 3. Steal some refactoring that solved some bugs on topic-read-leniently, so it stands to reason that we should apply it here already
1 parent dee6768 commit 695e82f

10 files changed

Lines changed: 589 additions & 123 deletions

File tree

include/openPMD/IO/AbstractIOHandler.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,28 @@ namespace internal
121121
FlushParams const defaultFlushParams{};
122122

123123
struct ParsedFlushParams;
124+
125+
/**
126+
* Some parts of the openPMD object model are read-only when accessing
127+
* a Series in Access::READ_ONLY mode, notably Containers and Attributes.
128+
* They are filled at parse time and not modified afterwards.
129+
* Such state-changing operations are hence allowed under either of two
130+
* conditions:
131+
* 1) The Series is opened in an open mode that allows writing in any way.
132+
* (Currently any but Access::READ_ONLY).
133+
* 2) The Series is in Parsing state. This way, modifying the open mode
134+
* during parsing can be avoided.
135+
*/
136+
enum class SeriesStatus : unsigned char
137+
{
138+
Default, ///< Mutability of objects in the openPMD object model is
139+
///< determined by the open mode (Access enum), normal state in
140+
///< which the user interacts with the Series.
141+
Parsing ///< All objects in the openPMD object model are temporarily
142+
///< mutable to allow inserting newly-parsed data.
143+
///< Special state only active while internal routines are
144+
///< running.
145+
};
124146
} // namespace internal
125147

126148
/** Interface for communicating between logical and physically persistent data.
@@ -192,6 +214,7 @@ class AbstractIOHandler
192214
// why do these need to be separate?
193215
Access const m_backendAccess;
194216
Access const m_frontendAccess;
217+
internal::SeriesStatus m_seriesStatus = internal::SeriesStatus::Default;
195218
std::queue<IOTask> m_work;
196219
}; // AbstractIOHandler
197220

include/openPMD/Iteration.hpp

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
#include "openPMD/backend/Attributable.hpp"
2929
#include "openPMD/backend/Container.hpp"
3030

31+
#include <cstdint>
32+
#include <deque>
3133
#include <optional>
34+
#include <tuple>
3235

3336
namespace openPMD
3437
{
@@ -282,14 +285,57 @@ class Iteration : public Attributable
282285
void readGorVBased(std::string const &groupPath, bool beginStep);
283286
void read_impl(std::string const &groupPath);
284287

288+
/**
289+
* Status after beginning an IO step. Currently includes:
290+
* * The advance status (OK, OVER, RANDOMACCESS)
291+
* * The opened iterations, in case the snapshot attribute is found
292+
*/
293+
struct BeginStepStatus
294+
{
295+
using AvailableIterations_t = std::optional<std::deque<uint64_t> >;
296+
297+
AdvanceStatus stepStatus{};
298+
/*
299+
* If the iteration attribute `snapshot` is present, the value of that
300+
* attribute. Otherwise empty.
301+
*/
302+
AvailableIterations_t iterationsInOpenedStep;
303+
304+
/*
305+
* Most of the time, the AdvanceStatus part of this struct is what we
306+
* need, so let's make it easy to access.
307+
*/
308+
inline operator AdvanceStatus() const
309+
{
310+
return stepStatus;
311+
}
312+
313+
/*
314+
* Support for std::tie()
315+
*/
316+
inline operator std::tuple<AdvanceStatus &, AvailableIterations_t &>()
317+
{
318+
return std::tuple<AdvanceStatus &, AvailableIterations_t &>{
319+
stepStatus, iterationsInOpenedStep};
320+
}
321+
};
322+
285323
/**
286324
* @brief Begin an IO step on the IO file (or file-like object)
287325
* containing this iteration. In case of group-based iteration
288326
* layout, this will be the complete Series.
289327
*
290-
* @return AdvanceStatus
328+
* @return BeginStepStatus
329+
*/
330+
BeginStepStatus beginStep(bool reread);
331+
332+
/*
333+
* Iteration-independent variant for beginStep().
334+
* Useful in group-based iteration encoding where the Iteration will only
335+
* be known after opening the step.
291336
*/
292-
AdvanceStatus beginStep(bool reread);
337+
static BeginStepStatus
338+
beginStep(std::optional<Iteration> thisObject, Series &series, bool reread);
293339

294340
/**
295341
* @brief End an IO step on the IO file (or file-like object)

include/openPMD/ReadIterations.hpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include "openPMD/Iteration.hpp"
2424
#include "openPMD/Series.hpp"
2525

26+
#include <deque>
27+
#include <iostream>
2628
#include <optional>
2729

2830
namespace openPMD
@@ -54,7 +56,8 @@ class SeriesIterator
5456
using maybe_series_t = std::optional<Series>;
5557

5658
maybe_series_t m_series;
57-
iteration_index_t m_currentIteration = 0;
59+
std::deque<iteration_index_t> m_iterationsInCurrentStep;
60+
uint64_t m_currentIteration{};
5861

5962
public:
6063
//! construct the end() iterator
@@ -71,6 +74,39 @@ class SeriesIterator
7174
bool operator!=(SeriesIterator const &other) const;
7275

7376
static SeriesIterator end();
77+
78+
private:
79+
inline bool setCurrentIteration()
80+
{
81+
if (m_iterationsInCurrentStep.empty())
82+
{
83+
std::cerr << "[ReadIterations] Encountered a step without "
84+
"iterations. Closing the Series."
85+
<< std::endl;
86+
*this = end();
87+
return false;
88+
}
89+
m_currentIteration = *m_iterationsInCurrentStep.begin();
90+
return true;
91+
}
92+
93+
inline std::optional<uint64_t> peekCurrentIteration()
94+
{
95+
if (m_iterationsInCurrentStep.empty())
96+
{
97+
return std::nullopt;
98+
}
99+
else
100+
{
101+
return {*m_iterationsInCurrentStep.begin()};
102+
}
103+
}
104+
105+
std::optional<SeriesIterator *> nextIterationInStep();
106+
107+
std::optional<SeriesIterator *> nextStep();
108+
109+
std::optional<SeriesIterator *> loopBody();
74110
};
75111

76112
/**

include/openPMD/Series.hpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#endif
3939

4040
#include <cstdint>
41+
#include <deque>
4142
#include <map>
4243
#include <optional>
4344
#include <set>
@@ -584,8 +585,10 @@ OPENPMD_private
584585
* Note on re-parsing of a Series:
585586
* If init == false, the parsing process will seek for new
586587
* Iterations/Records/Record Components etc.
588+
* If series.iterations contains the attribute `snapshot`, returns its
589+
* value.
587590
*/
588-
void readGorVBased(bool init = true);
591+
std::optional<std::deque<uint64_t> > readGorVBased(bool init = true);
589592
void readBase();
590593
std::string iterationFilename(uint64_t i);
591594

@@ -636,13 +639,21 @@ OPENPMD_private
636639
iterations_iterator it,
637640
Iteration &iteration);
638641

642+
AdvanceStatus advance(AdvanceMode mode);
643+
639644
/**
640645
* @brief Called at the end of an IO step to store the iterations defined
641646
* in the IO step to the snapshot attribute.
642647
*
643648
* @param doFlush If true, flush the IO handler.
644649
*/
645650
void flushStep(bool doFlush);
651+
652+
/*
653+
* Returns the current content of the /data/snapshot attribute.
654+
* (We could also add this to the public API some time)
655+
*/
656+
std::optional<std::vector<uint64_t> > currentSnapshot() const;
646657
}; // Series
647658
} // namespace openPMD
648659

include/openPMD/backend/Attributable.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,9 @@ inline bool Attributable::setAttributeImpl(
463463
internal::attr_value_check(key, value, setAttributeMode);
464464

465465
auto &attri = get();
466-
if (IOHandler() && Access::READ_ONLY == IOHandler()->m_frontendAccess)
466+
if (IOHandler() &&
467+
IOHandler()->m_seriesStatus == internal::SeriesStatus::Default &&
468+
Access::READ_ONLY == IOHandler()->m_frontendAccess)
467469
{
468470
auxiliary::OutOfRangeMsg const out_of_range_msg(
469471
"Attribute", "can not be set (read-only).");

include/openPMD/backend/Container.hpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,9 @@ class Container : public Attributable
288288
return it->second;
289289
else
290290
{
291-
if (Access::READ_ONLY == IOHandler()->m_frontendAccess)
291+
if (IOHandler()->m_seriesStatus !=
292+
internal::SeriesStatus::Parsing &&
293+
Access::READ_ONLY == IOHandler()->m_frontendAccess)
292294
{
293295
auxiliary::OutOfRangeMsg const out_of_range_msg;
294296
throw std::out_of_range(out_of_range_msg(key));
@@ -321,7 +323,9 @@ class Container : public Attributable
321323
return it->second;
322324
else
323325
{
324-
if (Access::READ_ONLY == IOHandler()->m_frontendAccess)
326+
if (IOHandler()->m_seriesStatus !=
327+
internal::SeriesStatus::Parsing &&
328+
Access::READ_ONLY == IOHandler()->m_frontendAccess)
325329
{
326330
auxiliary::OutOfRangeMsg out_of_range_msg;
327331
throw std::out_of_range(out_of_range_msg(key));

src/Iteration.cpp

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -573,49 +573,92 @@ void Iteration::read_impl(std::string const &groupPath)
573573
readAttributes(ReadMode::FullyReread);
574574
}
575575

576-
AdvanceStatus Iteration::beginStep(bool reread)
576+
auto Iteration::beginStep(bool reread) -> BeginStepStatus
577577
{
578-
using IE = IterationEncoding;
578+
BeginStepStatus res;
579579
auto series = retrieveSeries();
580+
return beginStep({*this}, series, reread);
581+
}
582+
583+
auto Iteration::beginStep(
584+
std::optional<Iteration> thisObject, Series &series, bool reread)
585+
-> BeginStepStatus
586+
{
587+
BeginStepStatus res;
588+
using IE = IterationEncoding;
580589
// Initialize file with this to quiet warnings
581590
// The following switch is comprehensive
582591
internal::AttributableData *file = nullptr;
583592
switch (series.iterationEncoding())
584593
{
585594
case IE::fileBased:
586-
file = &Attributable::get();
595+
if (thisObject.has_value())
596+
{
597+
file = &static_cast<Attributable &>(*thisObject).get();
598+
}
599+
else
600+
{
601+
throw error::Internal(
602+
"Advancing a step in file-based iteration encoding is "
603+
"iteration-specific.");
604+
}
587605
break;
588606
case IE::groupBased:
589607
case IE::variableBased:
590608
file = &series.get();
591609
break;
592610
}
593-
AdvanceStatus status = series.advance(
594-
AdvanceMode::BEGINSTEP, *file, series.indexOf(*this), *this);
595-
if (status != AdvanceStatus::OK)
611+
612+
AdvanceStatus status;
613+
if (thisObject.has_value())
596614
{
597-
return status;
615+
status = series.advance(
616+
AdvanceMode::BEGINSTEP,
617+
*file,
618+
series.indexOf(*thisObject),
619+
*thisObject);
620+
}
621+
else
622+
{
623+
status = series.advance(AdvanceMode::BEGINSTEP);
624+
}
625+
626+
switch (status)
627+
{
628+
case AdvanceStatus::OVER:
629+
res.stepStatus = status;
630+
return res;
631+
case AdvanceStatus::OK:
632+
case AdvanceStatus::RANDOMACCESS:
633+
break;
598634
}
599635

600636
// re-read -> new datasets might be available
601-
if (reread &&
637+
auto IOHandl = series.IOHandler();
638+
if (reread && status != AdvanceStatus::RANDOMACCESS &&
602639
(series.iterationEncoding() == IE::groupBased ||
603640
series.iterationEncoding() == IE::variableBased) &&
604-
(this->IOHandler()->m_frontendAccess == Access::READ_ONLY ||
605-
this->IOHandler()->m_frontendAccess == Access::READ_WRITE))
641+
(IOHandl->m_frontendAccess == Access::READ_ONLY ||
642+
IOHandl->m_frontendAccess == Access::READ_WRITE))
606643
{
607-
switch (IOHandler()->m_frontendAccess)
644+
switch (IOHandl->m_frontendAccess)
608645
{
609646
case Access::READ_ONLY:
610647
case Access::READ_WRITE: {
611648
bool previous = series.iterations.written();
612649
series.iterations.written() = false;
613-
auto oldType = this->IOHandler()->m_frontendAccess;
614-
auto newType =
615-
const_cast<Access *>(&this->IOHandler()->m_frontendAccess);
616-
*newType = Access::READ_WRITE;
617-
series.readGorVBased(false);
618-
*newType = oldType;
650+
auto oldStatus = IOHandl->m_seriesStatus;
651+
IOHandl->m_seriesStatus = internal::SeriesStatus::Parsing;
652+
try
653+
{
654+
res.iterationsInOpenedStep = series.readGorVBased(false);
655+
}
656+
catch (...)
657+
{
658+
IOHandl->m_seriesStatus = oldStatus;
659+
throw;
660+
}
661+
IOHandl->m_seriesStatus = oldStatus;
619662
series.iterations.written() = previous;
620663
break;
621664
}
@@ -626,7 +669,8 @@ AdvanceStatus Iteration::beginStep(bool reread)
626669
}
627670
}
628671

629-
return status;
672+
res.stepStatus = status;
673+
return res;
630674
}
631675

632676
void Iteration::endStep()
@@ -648,6 +692,7 @@ void Iteration::endStep()
648692
}
649693
// @todo filebased check
650694
series.advance(AdvanceMode::ENDSTEP, *file, series.indexOf(*this), *this);
695+
series.get().m_currentlyActiveIterations.clear();
651696
}
652697

653698
StepStatus Iteration::getStepStatus()
@@ -731,9 +776,8 @@ void Iteration::runDeferredParseAccess()
731776
}
732777
auto const &deferred = it.m_deferredParseAccess.value();
733778

734-
auto oldAccess = IOHandler()->m_frontendAccess;
735-
auto newAccess = const_cast<Access *>(&IOHandler()->m_frontendAccess);
736-
*newAccess = Access::READ_WRITE;
779+
auto oldStatus = IOHandler()->m_seriesStatus;
780+
IOHandler()->m_seriesStatus = internal::SeriesStatus::Parsing;
737781
try
738782
{
739783
if (deferred.fileBased)
@@ -750,12 +794,12 @@ void Iteration::runDeferredParseAccess()
750794
{
751795
// reset this thing
752796
it.m_deferredParseAccess = std::optional<DeferredParseAccess>();
753-
*newAccess = oldAccess;
797+
IOHandler()->m_seriesStatus = oldStatus;
754798
throw;
755799
}
756800
// reset this thing
757801
it.m_deferredParseAccess = std::optional<DeferredParseAccess>();
758-
*newAccess = oldAccess;
802+
IOHandler()->m_seriesStatus = oldStatus;
759803
break;
760804
}
761805
case Access::CREATE:

0 commit comments

Comments
 (0)