Tidy: modernize-use-nullptr
[alexxy/gromacs.git] / src / gromacs / analysisdata / datastorage.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012,2013,2014,2015,2016,2017, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements classes in datastorage.h and paralleloptions.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_analysisdata
41  */
42 #include "gmxpre.h"
43
44 #include "datastorage.h"
45
46 #include <algorithm>
47 #include <iterator>
48 #include <limits>
49 #include <memory>
50 #include <vector>
51
52 #include "gromacs/analysisdata/abstractdata.h"
53 #include "gromacs/analysisdata/dataframe.h"
54 #include "gromacs/analysisdata/datamodulemanager.h"
55 #include "gromacs/analysisdata/paralleloptions.h"
56 #include "gromacs/utility/exceptions.h"
57 #include "gromacs/utility/gmxassert.h"
58
59 namespace gmx
60 {
61
62 /********************************************************************
63  * AnalysisDataParallelOptions
64  */
65
66 AnalysisDataParallelOptions::AnalysisDataParallelOptions()
67     : parallelizationFactor_(1)
68 {
69 }
70
71
72 AnalysisDataParallelOptions::AnalysisDataParallelOptions(int parallelizationFactor)
73     : parallelizationFactor_(parallelizationFactor)
74 {
75     GMX_RELEASE_ASSERT(parallelizationFactor >= 1,
76                        "Invalid parallelization factor");
77 }
78
79
80 /********************************************************************
81  * AnalysisDataStorageImpl declaration
82  */
83
84 namespace internal
85 {
86
87 //! Smart pointer type for managing a storage frame builder.
88 typedef std::unique_ptr<AnalysisDataStorageFrame>
89     AnalysisDataFrameBuilderPointer;
90
91 /*! \internal \brief
92  * Private implementation class for AnalysisDataStorage.
93  *
94  * \ingroup module_analysisdata
95  */
96 class AnalysisDataStorageImpl
97 {
98     public:
99         //! Smart pointer type for managing a stored frame.
100         typedef std::unique_ptr<AnalysisDataStorageFrameData> FramePointer;
101
102         //! Shorthand for a list of data frames that are currently stored.
103         typedef std::vector<FramePointer> FrameList;
104         //! Shorthand for a list of currently unused storage frame builders.
105         typedef std::vector<AnalysisDataFrameBuilderPointer> FrameBuilderList;
106
107         AnalysisDataStorageImpl();
108
109         //! Returns whether the storage is set to use multipoint data.
110         bool isMultipoint() const;
111         /*! \brief
112          * Whether storage of all frames has been requested.
113          *
114          * Storage of all frames also works as expected if \a storageLimit_ is
115          * used in comparisons directly, but this method should be used to
116          * check how to manage \a frames_.
117          */
118         bool storeAll() const
119         {
120             return storageLimit_ == std::numeric_limits<int>::max();
121         }
122         //! Returns the index of the oldest frame that may be currently stored.
123         int firstStoredIndex() const;
124         //! Returns the index of the first frame that is not fully notified.
125         int firstUnnotifiedIndex() const { return firstUnnotifiedIndex_; }
126         /*! \brief
127          * Computes index into \a frames_ for accessing frame \p index.
128          *
129          * \param[in]  index  Zero-based frame index.
130          * \retval  -1 if \p index is not available in \a frames_.
131          *
132          * Does not throw.
133          */
134         int computeStorageLocation(int index) const;
135
136         /*! \brief
137          * Computes an index into \a frames_ that is one past the last frame
138          * stored.
139          *
140          * Does not throw.
141          */
142         size_t endStorageLocation() const;
143
144         /*! \brief
145          * Extends \a frames_ to a new size.
146          *
147          * \throws std::bad_alloc if out of memory.
148          */
149         void extendBuffer(size_t newSize);
150         /*! \brief
151          * Remove oldest frame from the storage to make space for a new one.
152          *
153          * Increments \a firstFrameLocation_ and reinitializes the frame that
154          * was made unavailable by this operation.
155          *
156          * Does not throw.
157          *
158          * \see frames_
159          */
160         void rotateBuffer();
161
162         /*! \brief
163          * Returns a frame builder object for use with a new frame.
164          *
165          * \throws std::bad_alloc if out of memory.
166          */
167         AnalysisDataFrameBuilderPointer getFrameBuilder();
168
169         /*! \brief
170          * Returns whether notifications should be immediately fired.
171          *
172          * This is used to optimize multipoint handling for non-parallel cases,
173          * where it is not necessary to store even a single frame.
174          *
175          * Does not throw.
176          */
177         bool shouldNotifyImmediately() const
178         {
179             return isMultipoint() && storageLimit_ == 0 && pendingLimit_ == 1;
180         }
181         /*! \brief
182          * Returns whether data needs to be stored at all.
183          *
184          * This is used to optimize multipoint handling for parallel cases
185          * (where shouldNotifyImmediately() returns false),
186          * where it is not necessary to store even a single frame.
187          *
188          * \todo
189          * This could be extended to non-multipoint data as well.
190          *
191          * Does not throw.
192          */
193         bool needStorage() const
194         {
195             return storageLimit_ > 0 || (pendingLimit_ > 1 && modules_->hasSerialModules());
196         }
197         //! Implementation for AnalysisDataStorage::finishFrame().
198         void finishFrame(int index);
199         /*! \brief
200          * Implementation for AnalysisDataStorage::finishFrameSerial().
201          */
202         void finishFrameSerial(int index);
203
204
205         //! Parent data object to access data dimensionality etc.
206         const AbstractAnalysisData *data_;
207         //! Manager to use for notification calls.
208         AnalysisDataModuleManager  *modules_;
209         /*! \brief
210          * Number of past frames that need to be stored.
211          *
212          * Always non-negative.  If storage of all frames has been requested,
213          * this is set to a large number.
214          */
215         int                     storageLimit_;
216         /*! \brief
217          * Number of future frames that may need to be started.
218          *
219          * Should always be at least one.
220          *
221          * \todo
222          * Get rid of this alltogether, as it is no longer used much.
223          *
224          * \see AnalysisDataStorage::startFrame()
225          */
226         int                     pendingLimit_;
227         /*! \brief
228          * Data frames that are currently stored.
229          *
230          * If storage of all frames has been requested, this is simply a vector
231          * of frames up to the latest frame that has been started.
232          * In this case, \a firstFrameLocation_ is always zero.
233          *
234          * If storage of all frames is not requested, this is a ring buffer of
235          * frames of size \c n=storageLimit_+pendingLimit_+1.  If a frame with
236          * index \c index is currently stored, its location is
237          * \c index%frames_.size().
238          * When at most \a storageLimit_ first frames have been finished,
239          * this contains storage for the first \c n-1 frames.
240          * When more than \a storageLimit_ first frames have been finished,
241          * the oldest stored frame is stored in the location
242          * \a firstFrameLocation_, and \a storageLimit_ frames starting from
243          * this location are the last finished frames.  \a pendingLimit_ frames
244          * follow, and some of these may be in progress or finished.
245          * There is always one unused frame in the buffer, which is initialized
246          * such that when \a firstFrameLocation_ is incremented, it becomes
247          * valid.  This makes it easier to rotate the buffer in concurrent
248          * access scenarions (which are not yet otherwise implemented).
249          */
250         FrameList               frames_;
251         //! Location of oldest frame in \a frames_.
252         size_t                  firstFrameLocation_;
253         //! Index of the first frame that is not fully notified.
254         int                     firstUnnotifiedIndex_;
255         /*! \brief
256          * Currently unused frame builders.
257          *
258          * The builders are cached to avoid repeatedly allocating memory for
259          * them.  Typically, there are as many builders as there are concurrent
260          * users of the storage object.  Whenever a frame is started, a builder
261          * is pulled from this pool by getFrameBuilder() (a new one is created
262          * if none are available), and assigned for that frame.  When that
263          * frame is finished, the builder is returned to this pool.
264          */
265         FrameBuilderList        builders_;
266         /*! \brief
267          * Index of next frame that will be added to \a frames_.
268          *
269          * If all frames are not stored, this will be the index of the unused
270          * frame (see \a frames_).
271          */
272         int                     nextIndex_;
273 };
274
275 /********************************************************************
276  * AnalysisDataStorageFrameImpl declaration
277  */
278
279 /*! \internal
280  * \brief
281  * Internal representation for a single stored frame.
282  *
283  * It is implemented such that the frame header is always valid, i.e.,
284  * header().isValid() returns always true.
285  *
286  * Methods in this class do not throw unless otherwise indicated.
287  *
288  * \ingroup module_analysisdata
289  */
290 class AnalysisDataStorageFrameData
291 {
292     public:
293         //! Shorthand for a iterator into storage value containers.
294         typedef std::vector<AnalysisDataValue>::const_iterator ValueIterator;
295
296         //! Indicates what operations have been performed on a frame.
297         enum Status
298         {
299             eMissing,  //!< Frame has not yet been started.
300             eStarted,  //!< startFrame() has been called.
301             eFinished, //!< finishFrame() has been called.
302             eNotified  //!< Appropriate notifications have been sent.
303         };
304
305         /*! \brief
306          * Create a new storage frame.
307          *
308          * \param     storageImpl  Storage object this frame belongs to.
309          * \param[in] index        Zero-based index for the frame.
310          */
311         AnalysisDataStorageFrameData(AnalysisDataStorageImpl *storageImpl,
312                                      int                      index);
313
314         //! Whether the frame has been started with startFrame().
315         bool isStarted() const { return status_ >= eStarted; }
316         //! Whether the frame has been finished with finishFrame().
317         bool isFinished() const { return status_ >= eFinished; }
318         //! Whether all notifications have been sent.
319         bool isNotified() const { return status_ >= eNotified; }
320         //! Whether the frame is ready to be available outside the storage.
321         bool isAvailable() const { return status_ >= eFinished; }
322
323         //! Marks the frame as notified.
324         void markNotified() { status_ = eNotified; }
325
326         //! Returns the storage implementation object.
327         AnalysisDataStorageImpl &storageImpl() const { return storageImpl_; }
328         //! Returns the underlying data object (for data dimensionalities etc.).
329         const AbstractAnalysisData &baseData() const { return *storageImpl().data_; }
330
331         //! Returns header for the frame.
332         const AnalysisDataFrameHeader &header() const { return header_; }
333         //! Returns zero-based index of the frame.
334         int frameIndex() const { return header().index(); }
335         //! Returns the number of point sets for the frame.
336         int pointSetCount() const { return pointSets_.size(); }
337
338         //! Clears the frame for reusing as a new frame.
339         void clearFrame(int newIndex);
340         /*! \brief
341          * Initializes the frame during AnalysisDataStorage::startFrame().
342          *
343          * \param[in] header  Header to use for the new frame.
344          * \param[in] builder Builder object to use.
345          */
346         void startFrame(const AnalysisDataFrameHeader   &header,
347                         AnalysisDataFrameBuilderPointer  builder);
348         //! Returns the builder for this frame.
349         AnalysisDataStorageFrame &builder() const
350         {
351             GMX_ASSERT(builder_, "Accessing builder for not-in-progress frame");
352             return *builder_;
353         }
354         /*! \brief
355          * Adds a new point set to this frame.
356          */
357         void addPointSet(int dataSetIndex, int firstColumn,
358                          ValueIterator begin, ValueIterator end);
359         /*! \brief
360          * Finalizes the frame during AnalysisDataStorage::finishFrame().
361          *
362          * \returns The builder object used by the frame, for reusing it for
363          *      other frames.
364          */
365         AnalysisDataFrameBuilderPointer finishFrame(bool bMultipoint);
366
367         //! Returns frame reference to this frame.
368         AnalysisDataFrameRef frameReference() const
369         {
370             return AnalysisDataFrameRef(header_, values_, pointSets_);
371         }
372         //! Returns point set reference to a given point set.
373         AnalysisDataPointSetRef pointSet(int index) const;
374
375     private:
376         //! Storage object that contains this frame.
377         AnalysisDataStorageImpl                &storageImpl_;
378         //! Header for the frame.
379         AnalysisDataFrameHeader                 header_;
380         //! Values for the frame.
381         std::vector<AnalysisDataValue>          values_;
382         //! Information about each point set in the frame.
383         std::vector<AnalysisDataPointSetInfo>   pointSets_;
384         /*! \brief
385          * Builder object for the frame.
386          *
387          * Non-NULL when the frame is in progress, i.e., has been started but
388          * not yet finished.
389          */
390         AnalysisDataFrameBuilderPointer         builder_;
391         //! In what state the frame currently is.
392         Status                                  status_;
393
394         GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisDataStorageFrameData);
395 };
396
397 /********************************************************************
398  * AnalysisDataStorageImpl implementation
399  */
400
401 AnalysisDataStorageImpl::AnalysisDataStorageImpl()
402     : data_(nullptr), modules_(nullptr),
403       storageLimit_(0), pendingLimit_(1),
404       firstFrameLocation_(0), firstUnnotifiedIndex_(0), nextIndex_(0)
405 {
406 }
407
408
409 bool
410 AnalysisDataStorageImpl::isMultipoint() const
411 {
412     GMX_ASSERT(data_ != NULL, "isMultipoint() called too early");
413     return data_->isMultipoint();
414 }
415
416
417 int
418 AnalysisDataStorageImpl::firstStoredIndex() const
419 {
420     return frames_[firstFrameLocation_]->frameIndex();
421 }
422
423
424 int
425 AnalysisDataStorageImpl::computeStorageLocation(int index) const
426 {
427     if (index < firstStoredIndex() || index >= nextIndex_)
428     {
429         return -1;
430     }
431     return index % frames_.size();
432 }
433
434
435 size_t
436 AnalysisDataStorageImpl::endStorageLocation() const
437 {
438     if (storeAll())
439     {
440         return frames_.size();
441     }
442     if (frames_[0]->frameIndex() == 0 || firstFrameLocation_ == 0)
443     {
444         return frames_.size() - 1;
445     }
446     return firstFrameLocation_ - 1;
447 }
448
449
450 void
451 AnalysisDataStorageImpl::extendBuffer(size_t newSize)
452 {
453     frames_.reserve(newSize);
454     while (frames_.size() < newSize)
455     {
456         frames_.push_back(FramePointer(new AnalysisDataStorageFrameData(this, nextIndex_)));
457         ++nextIndex_;
458     }
459     // The unused frame should not be included in the count.
460     if (!storeAll())
461     {
462         --nextIndex_;
463     }
464 }
465
466
467 void
468 AnalysisDataStorageImpl::rotateBuffer()
469 {
470     GMX_ASSERT(!storeAll(),
471                "No need to rotate internal buffer if everything is stored");
472     size_t prevFirst = firstFrameLocation_;
473     size_t nextFirst = prevFirst + 1;
474     if (nextFirst == frames_.size())
475     {
476         nextFirst = 0;
477     }
478     firstFrameLocation_ = nextFirst;
479     frames_[prevFirst]->clearFrame(nextIndex_ + 1);
480     ++nextIndex_;
481 }
482
483
484 AnalysisDataFrameBuilderPointer
485 AnalysisDataStorageImpl::getFrameBuilder()
486 {
487     if (builders_.empty())
488     {
489         return AnalysisDataFrameBuilderPointer(new AnalysisDataStorageFrame(*data_));
490     }
491     AnalysisDataFrameBuilderPointer builder(std::move(builders_.back()));
492     builders_.pop_back();
493     return builder;
494 }
495
496
497 void
498 AnalysisDataStorageImpl::finishFrame(int index)
499 {
500     const int storageIndex = computeStorageLocation(index);
501     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
502
503     AnalysisDataStorageFrameData &storedFrame = *frames_[storageIndex];
504     GMX_RELEASE_ASSERT(storedFrame.isStarted(),
505                        "finishFrame() called for frame before startFrame()");
506     GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
507                        "finishFrame() called twice for the same frame");
508     GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
509                        "Inconsistent internal frame indexing");
510     builders_.push_back(storedFrame.finishFrame(isMultipoint()));
511     modules_->notifyParallelFrameFinish(storedFrame.header());
512     if (pendingLimit_ == 1)
513     {
514         finishFrameSerial(index);
515     }
516 }
517
518
519 void
520 AnalysisDataStorageImpl::finishFrameSerial(int index)
521 {
522     GMX_RELEASE_ASSERT(index == firstUnnotifiedIndex_,
523                        "Out of order finisFrameSerial() calls");
524     const int storageIndex = computeStorageLocation(index);
525     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
526
527     AnalysisDataStorageFrameData &storedFrame = *frames_[storageIndex];
528     GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
529                        "Inconsistent internal frame indexing");
530     GMX_RELEASE_ASSERT(storedFrame.isFinished(),
531                        "finishFrameSerial() called before finishFrame()");
532     GMX_RELEASE_ASSERT(!storedFrame.isNotified(),
533                        "finishFrameSerial() called twice for the same frame");
534     // Increment before the notifications to make the frame available
535     // in the module callbacks.
536     ++firstUnnotifiedIndex_;
537     if (shouldNotifyImmediately())
538     {
539         modules_->notifyFrameFinish(storedFrame.header());
540     }
541     else
542     {
543         modules_->notifyFrameStart(storedFrame.header());
544         for (int j = 0; j < storedFrame.pointSetCount(); ++j)
545         {
546             modules_->notifyPointsAdd(storedFrame.pointSet(j));
547         }
548         modules_->notifyFrameFinish(storedFrame.header());
549     }
550     storedFrame.markNotified();
551     if (storedFrame.frameIndex() >= storageLimit_)
552     {
553         rotateBuffer();
554     }
555 }
556
557
558 /********************************************************************
559  * AnalysisDataStorageFrame implementation
560  */
561
562 AnalysisDataStorageFrameData::AnalysisDataStorageFrameData(
563         AnalysisDataStorageImpl *storageImpl,
564         int                      index)
565     : storageImpl_(*storageImpl), header_(index, 0.0, 0.0), status_(eMissing)
566 {
567     GMX_RELEASE_ASSERT(storageImpl->data_ != nullptr,
568                        "Storage frame constructed before data started");
569     // With non-multipoint data, the point set structure is static,
570     // so initialize it only once here.
571     if (!baseData().isMultipoint())
572     {
573         int offset = 0;
574         for (int i = 0; i < baseData().dataSetCount(); ++i)
575         {
576             int columnCount = baseData().columnCount(i);
577             pointSets_.emplace_back(offset, columnCount, i, 0);
578             offset += columnCount;
579         }
580     }
581 }
582
583
584 void
585 AnalysisDataStorageFrameData::clearFrame(int newIndex)
586 {
587     GMX_RELEASE_ASSERT(!builder_, "Should not clear an in-progress frame");
588     status_ = eMissing;
589     header_ = AnalysisDataFrameHeader(newIndex, 0.0, 0.0);
590     values_.clear();
591     if (baseData().isMultipoint())
592     {
593         pointSets_.clear();
594     }
595 }
596
597
598 void
599 AnalysisDataStorageFrameData::startFrame(
600         const AnalysisDataFrameHeader   &header,
601         AnalysisDataFrameBuilderPointer  builder)
602 {
603     status_         = eStarted;
604     header_         = header;
605     builder_        = std::move(builder);
606     builder_->data_ = this;
607     builder_->selectDataSet(0);
608 }
609
610
611 void
612 AnalysisDataStorageFrameData::addPointSet(int dataSetIndex, int firstColumn,
613                                           ValueIterator begin, ValueIterator end)
614 {
615     const int                valueCount = end - begin;
616     AnalysisDataPointSetInfo pointSetInfo(0, valueCount,
617                                           dataSetIndex, firstColumn);
618     AnalysisDataPointSetRef  pointSet(header(), pointSetInfo,
619                                       constArrayRefFromVector<AnalysisDataValue>(begin, end));
620     storageImpl().modules_->notifyParallelPointsAdd(pointSet);
621     if (storageImpl().shouldNotifyImmediately())
622     {
623         storageImpl().modules_->notifyPointsAdd(pointSet);
624     }
625     else if (storageImpl().needStorage())
626     {
627         pointSets_.emplace_back(values_.size(), valueCount,
628                                 dataSetIndex, firstColumn);
629         std::copy(begin, end, std::back_inserter(values_));
630     }
631 }
632
633
634 AnalysisDataFrameBuilderPointer
635 AnalysisDataStorageFrameData::finishFrame(bool bMultipoint)
636 {
637     status_ = eFinished;
638     if (!bMultipoint)
639     {
640         GMX_RELEASE_ASSERT(static_cast<int>(pointSets_.size()) == baseData().dataSetCount(),
641                            "Point sets created for non-multipoint data");
642         values_ = builder_->values_;
643         builder_->clearValues();
644         for (int i = 0; i < pointSetCount(); ++i)
645         {
646             storageImpl().modules_->notifyParallelPointsAdd(pointSet(i));
647         }
648     }
649     else
650     {
651         GMX_RELEASE_ASSERT(!builder_->bPointSetInProgress_,
652                            "Unfinished point set");
653     }
654     AnalysisDataFrameBuilderPointer builder(std::move(builder_));
655     builder_.reset();
656     return builder;
657 }
658
659
660 AnalysisDataPointSetRef
661 AnalysisDataStorageFrameData::pointSet(int index) const
662 {
663     GMX_ASSERT(index >= 0 && index < pointSetCount(),
664                "Invalid point set index");
665     return AnalysisDataPointSetRef(
666             header_, pointSets_[index],
667             constArrayRefFromVector<AnalysisDataValue>(values_.begin(), values_.end()));
668 }
669
670 }   // namespace internal
671
672 /********************************************************************
673  * AnalysisDataStorageFrame
674  */
675
676 AnalysisDataStorageFrame::AnalysisDataStorageFrame(
677         const AbstractAnalysisData &data)
678     : data_(nullptr), currentDataSet_(0), currentOffset_(0),
679       columnCount_(data.columnCount(0)), bPointSetInProgress_(false)
680 {
681     int totalColumnCount = 0;
682     for (int i = 0; i < data.dataSetCount(); ++i)
683     {
684         totalColumnCount += data.columnCount(i);
685     }
686     values_.resize(totalColumnCount);
687 }
688
689
690 AnalysisDataStorageFrame::~AnalysisDataStorageFrame()
691 {
692 }
693
694
695 void
696 AnalysisDataStorageFrame::clearValues()
697 {
698     if (bPointSetInProgress_)
699     {
700         std::vector<AnalysisDataValue>::iterator i;
701         for (i = values_.begin(); i != values_.end(); ++i)
702         {
703             i->clear();
704         }
705     }
706     bPointSetInProgress_ = false;
707 }
708
709
710 void
711 AnalysisDataStorageFrame::selectDataSet(int index)
712 {
713     GMX_RELEASE_ASSERT(data_ != nullptr, "Invalid frame accessed");
714     const AbstractAnalysisData &baseData = data_->baseData();
715     GMX_RELEASE_ASSERT(index >= 0 && index < baseData.dataSetCount(),
716                        "Out of range data set index");
717     GMX_RELEASE_ASSERT(!baseData.isMultipoint() || !bPointSetInProgress_,
718                        "Point sets in multipoint data cannot span data sets");
719     currentDataSet_ = index;
720     currentOffset_  = 0;
721     // TODO: Consider precalculating.
722     for (int i = 0; i < index; ++i)
723     {
724         currentOffset_ += baseData.columnCount(i);
725     }
726     columnCount_    = baseData.columnCount(index);
727 }
728
729
730 void
731 AnalysisDataStorageFrame::finishPointSet()
732 {
733     GMX_RELEASE_ASSERT(data_ != nullptr, "Invalid frame accessed");
734     GMX_RELEASE_ASSERT(data_->baseData().isMultipoint(),
735                        "Should not be called for non-multipoint data");
736     if (bPointSetInProgress_)
737     {
738         std::vector<AnalysisDataValue>::const_iterator begin
739             = values_.begin() + currentOffset_;
740         std::vector<AnalysisDataValue>::const_iterator end
741             = begin + columnCount_;
742         int firstColumn = 0;
743         while (begin != end && !begin->isSet())
744         {
745             ++begin;
746             ++firstColumn;
747         }
748         while (end != begin && !(end-1)->isSet())
749         {
750             --end;
751         }
752         if (begin == end)
753         {
754             firstColumn = 0;
755         }
756         data_->addPointSet(currentDataSet_, firstColumn, begin, end);
757     }
758     clearValues();
759 }
760
761
762 void
763 AnalysisDataStorageFrame::finishFrame()
764 {
765     GMX_RELEASE_ASSERT(data_ != nullptr, "Invalid frame accessed");
766     data_->storageImpl().finishFrame(data_->frameIndex());
767 }
768
769
770 /********************************************************************
771  * AnalysisDataStorage
772  */
773
774 AnalysisDataStorage::AnalysisDataStorage()
775     : impl_(new Impl())
776 {
777 }
778
779
780 AnalysisDataStorage::~AnalysisDataStorage()
781 {
782 }
783
784
785 int
786 AnalysisDataStorage::frameCount() const
787 {
788     return impl_->firstUnnotifiedIndex();
789 }
790
791
792 AnalysisDataFrameRef
793 AnalysisDataStorage::tryGetDataFrame(int index) const
794 {
795     int storageIndex = impl_->computeStorageLocation(index);
796     if (storageIndex == -1)
797     {
798         return AnalysisDataFrameRef();
799     }
800     const internal::AnalysisDataStorageFrameData &storedFrame
801         = *impl_->frames_[storageIndex];
802     if (!storedFrame.isAvailable())
803     {
804         return AnalysisDataFrameRef();
805     }
806     return storedFrame.frameReference();
807 }
808
809
810 bool
811 AnalysisDataStorage::requestStorage(int nframes)
812 {
813     // Handle the case when everything needs to be stored.
814     if (nframes == -1)
815     {
816         impl_->storageLimit_ = std::numeric_limits<int>::max();
817         return true;
818     }
819     // Check whether an earlier call has requested more storage.
820     if (nframes < impl_->storageLimit_)
821     {
822         return true;
823     }
824     impl_->storageLimit_ = nframes;
825     return true;
826 }
827
828
829 void
830 AnalysisDataStorage::startDataStorage(AbstractAnalysisData      *data,
831                                       AnalysisDataModuleManager *modules)
832 {
833     modules->notifyDataStart(data);
834     // Data needs to be set before calling extendBuffer()
835     impl_->data_    = data;
836     impl_->modules_ = modules;
837     if (!impl_->storeAll())
838     {
839         // 2 = pending limit (1) + 1
840         impl_->extendBuffer(impl_->storageLimit_ + 2);
841     }
842 }
843
844
845 void
846 AnalysisDataStorage::startParallelDataStorage(
847         AbstractAnalysisData              *data,
848         AnalysisDataModuleManager         *modules,
849         const AnalysisDataParallelOptions &options)
850 {
851     const int pendingLimit = options.parallelizationFactor();
852     impl_->pendingLimit_   = pendingLimit;
853     modules->notifyParallelDataStart(data, options);
854     // Data needs to be set before calling extendBuffer()
855     impl_->data_    = data;
856     impl_->modules_ = modules;
857     if (!impl_->storeAll())
858     {
859         impl_->extendBuffer(impl_->storageLimit_ + pendingLimit + 1);
860     }
861 }
862
863
864 AnalysisDataStorageFrame &
865 AnalysisDataStorage::startFrame(const AnalysisDataFrameHeader &header)
866 {
867     GMX_ASSERT(header.isValid(), "Invalid header");
868     internal::AnalysisDataStorageFrameData *storedFrame;
869     if (impl_->storeAll())
870     {
871         size_t size = header.index() + 1;
872         if (impl_->frames_.size() < size)
873         {
874             impl_->extendBuffer(size);
875         }
876         storedFrame = impl_->frames_[header.index()].get();
877     }
878     else
879     {
880         int storageIndex = impl_->computeStorageLocation(header.index());
881         if (storageIndex == -1)
882         {
883             GMX_THROW(APIError("Out of bounds frame index"));
884         }
885         storedFrame = impl_->frames_[storageIndex].get();
886     }
887     GMX_RELEASE_ASSERT(!storedFrame->isStarted(),
888                        "startFrame() called twice for the same frame");
889     GMX_RELEASE_ASSERT(storedFrame->frameIndex() == header.index(),
890                        "Inconsistent internal frame indexing");
891     storedFrame->startFrame(header, impl_->getFrameBuilder());
892     impl_->modules_->notifyParallelFrameStart(header);
893     if (impl_->shouldNotifyImmediately())
894     {
895         impl_->modules_->notifyFrameStart(header);
896     }
897     return storedFrame->builder();
898 }
899
900
901 AnalysisDataStorageFrame &
902 AnalysisDataStorage::startFrame(int index, real x, real dx)
903 {
904     return startFrame(AnalysisDataFrameHeader(index, x, dx));
905 }
906
907
908 AnalysisDataStorageFrame &
909 AnalysisDataStorage::currentFrame(int index)
910 {
911     const int storageIndex = impl_->computeStorageLocation(index);
912     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
913
914     internal::AnalysisDataStorageFrameData &storedFrame = *impl_->frames_[storageIndex];
915     GMX_RELEASE_ASSERT(storedFrame.isStarted(),
916                        "currentFrame() called for frame before startFrame()");
917     GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
918                        "currentFrame() called for frame after finishFrame()");
919     GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
920                        "Inconsistent internal frame indexing");
921     return storedFrame.builder();
922 }
923
924
925 void
926 AnalysisDataStorage::finishFrame(int index)
927 {
928     impl_->finishFrame(index);
929 }
930
931 void
932 AnalysisDataStorage::finishFrameSerial(int index)
933 {
934     if (impl_->pendingLimit_ > 1)
935     {
936         impl_->finishFrameSerial(index);
937     }
938 }
939
940 void
941 AnalysisDataStorage::finishDataStorage()
942 {
943     // TODO: Check that all frames have been finished etc.
944     impl_->builders_.clear();
945     impl_->modules_->notifyDataFinish();
946 }
947
948 } // namespace gmx