AbstractAnalysisData module handling into a separate class.
authorTeemu Murtola <teemu.murtola@gmail.com>
Sun, 16 Jun 2013 03:32:21 +0000 (06:32 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Thu, 5 Sep 2013 19:51:38 +0000 (21:51 +0200)
Move all logic related to maintaining the list of attached modules,
checking their compatibility with the data, and notifying them from
AbstractAnalysisData into a separate AnalysisDataModuleManager class.
AbstractAnalysisData now contains an instance of this class, and
provires a moduleManager() protected method for derived classes to
access it.

Module notification methods are no longer responsible of keeping the
frame count.  Instead, made the frameCount() method pure virtual and
added a method in AnalysisDataStorage to conveniently implement this
method.

Moved the responsibility of calling all data notification methods into
AnalysisDataStorage (including notifyDataStart() and
notifyDataFinish()). This allows modules that use AnalysisDataStorage to
not even know of the implementation details of the module manager.

Improved the semantics of setColumnCount() etc. such that they now
immediately check the compatibility of data modules and throw if there
is a problem.

Rationale:
 - Remove implementation details from the installed abstractdata.h
   header: AbstractAnalysisData is one of the main public interfaces in
   the module, and it exposed unnecessary complexity in the header. Now
   only the subclasses that actually need it see these details.
   In particular with #869, the module notification interface is only
   going to get more complex.
 - Reduce the responsibilities of AbstractAnalysisData: now it only
   keeps track of the dimensionality of the data (and notifies the
   module manager of relevant changes). Now it is closer to a pure
   interface, which is possibly should be.
 - Makes it possible to remove friendship between AbstractAnalysisData
   and AnalysisDataStorage. This allows further simplification in
   datastorage.cpp.

Related to #869.

Change-Id: Ic42d4b27bb6d1e445e3f74aa06ae52b354bf4403

18 files changed:
src/gromacs/analysisdata/abstractdata.cpp
src/gromacs/analysisdata/abstractdata.h
src/gromacs/analysisdata/analysisdata.cpp
src/gromacs/analysisdata/analysisdata.h
src/gromacs/analysisdata/arraydata.cpp
src/gromacs/analysisdata/arraydata.h
src/gromacs/analysisdata/datamodule.h
src/gromacs/analysisdata/datamodulemanager.cpp [new file with mode: 0644]
src/gromacs/analysisdata/datamodulemanager.h [new file with mode: 0644]
src/gromacs/analysisdata/dataproxy.cpp
src/gromacs/analysisdata/dataproxy.h
src/gromacs/analysisdata/datastorage.cpp
src/gromacs/analysisdata/datastorage.h
src/gromacs/analysisdata/modules/average.cpp
src/gromacs/analysisdata/modules/average.h
src/gromacs/analysisdata/modules/displacement.cpp
src/gromacs/analysisdata/modules/histogram.cpp
src/gromacs/analysisdata/modules/histogram.h

index 301665f0a18fd44db3b1446d312f4077752665b2..878341a9569e8c5d24f339c30dc8e8815e019459 100644 (file)
 
 #include <vector>
 
+#include "gromacs/analysisdata/dataframe.h"
 #include "gromacs/analysisdata/datamodule.h"
+#include "gromacs/analysisdata/datamodulemanager.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/gmxassert.h"
 #include "gromacs/utility/uniqueptr.h"
 
-#include "dataframe.h"
 #include "dataproxy.h"
 
 namespace gmx
@@ -66,138 +67,22 @@ namespace gmx
 class AbstractAnalysisData::Impl
 {
     public:
-        //! Shorthand for list of modules added to the data.
-        typedef std::vector<AnalysisDataModulePointer> ModuleList;
-
         Impl();
 
-        //! Returns whether any data set has more than one column.
-        bool isMultiColumn() const;
-
-        /*! \brief
-         * Checks whether a module is compatible with the data properties.
-         *
-         * \param[in] module Module to check.
-         * \throws    APIError if \p module is not compatible with the data.
-         *
-         * Does not check the actual data (e.g., missing values), but only the
-         * dimensionality and other preset properties of the data.
-         */
-        void checkModuleProperties(const AnalysisDataModuleInterface &module) const;
-
-        /*! \brief
-         * Present data already added to the data object to a module.
-         *
-         * \param[in] data   Data object to read data from.
-         * \param[in] module Module to present the data to.
-         * \throws    APIError if \p module is not compatible with the data.
-         * \throws    APIError if all data is not available through
-         *      getDataFrame().
-         * \throws    unspecified Any exception thrown by \p module in its data
-         *      notification methods.
-         *
-         * Uses getDataFrame() in \p data to access all data in the object, and
-         * calls the notification functions in \p module as if the module had
-         * been registered to the data object when the data was added.
-         */
-        void presentData(AbstractAnalysisData        *data,
-                         AnalysisDataModuleInterface *module);
-
         //! Column counts for each data set in the data.
-        std::vector<int>        columnCounts_;
+        std::vector<int>           columnCounts_;
         //! Whether the data is multipoint.
-        bool                    bMultipoint_;
-        //! List of modules added to the data.
-        ModuleList              modules_;
-        //! true if all modules support missing data.
-        bool                    bAllowMissing_;
-        //! Whether notifyDataStart() has been called.
-        mutable bool            bDataStart_;
-        //! Whether new data is being added.
-        mutable bool            bInData_;
-        //! Whether data for a frame is being added.
-        mutable bool            bInFrame_;
-        //! Index of the currently active frame.
-        mutable int             currIndex_;
-        /*! \brief
-         * Total number of frames in the data.
-         *
-         * The counter is incremented in notifyFrameFinish().
-         */
-        int                     nframes_;
+        bool                       bMultipoint_;
+        //! Manager for the added modules.
+        AnalysisDataModuleManager  modules_;
 };
 
 AbstractAnalysisData::Impl::Impl()
-    : bMultipoint_(false), bAllowMissing_(true),
-      bDataStart_(false), bInData_(false), bInFrame_(false),
-      currIndex_(-1), nframes_(0)
+    : bMultipoint_(false)
 {
     columnCounts_.push_back(0);
 }
 
-bool
-AbstractAnalysisData::Impl::isMultiColumn() const
-{
-    std::vector<int>::const_iterator i;
-    for (i = columnCounts_.begin(); i != columnCounts_.end(); ++i)
-    {
-        if (*i > 1)
-        {
-            return true;
-        }
-    }
-    return false;
-}
-
-//! Helper macro for testing module flags.
-#define TEST_MODULE_FLAG(flags, flagname) \
-    ((flags) & AnalysisDataModuleInterface::flagname)
-void
-AbstractAnalysisData::Impl::checkModuleProperties(
-        const AnalysisDataModuleInterface &module) const
-{
-    const int flags = module.flags();
-    if ((!TEST_MODULE_FLAG(flags, efAllowMulticolumn) && isMultiColumn()) ||
-        (!TEST_MODULE_FLAG(flags, efAllowMultipoint)  && bMultipoint_) ||
-        ( TEST_MODULE_FLAG(flags, efOnlyMultipoint)   && !bMultipoint_) ||
-        (!TEST_MODULE_FLAG(flags, efAllowMultipleDataSets)
-         && columnCounts_.size() > 1U))
-    {
-        GMX_THROW(APIError("Data module not compatible with data object properties"));
-    }
-}
-#undef TEST_MODULE_FLAGS
-
-void
-AbstractAnalysisData::Impl::presentData(AbstractAnalysisData        *data,
-                                        AnalysisDataModuleInterface *module)
-{
-    module->dataStarted(data);
-    bool bCheckMissing = bAllowMissing_
-        && !(module->flags() & AnalysisDataModuleInterface::efAllowMissing);
-    for (int i = 0; i < data->frameCount(); ++i)
-    {
-        AnalysisDataFrameRef frame = data->getDataFrame(i);
-        GMX_RELEASE_ASSERT(frame.isValid(), "Invalid data frame returned");
-        // TODO: Check all frames before doing anything for slightly better
-        // exception behavior.
-        if (bCheckMissing && !frame.allPresent())
-        {
-            GMX_THROW(APIError("Missing data not supported by a module"));
-        }
-        module->frameStarted(frame.header());
-        for (int j = 0; j < frame.pointSetCount(); ++j)
-        {
-            module->pointsAdded(frame.pointSet(j));
-        }
-        module->frameFinished(frame.header());
-    }
-    if (!bInData_)
-    {
-        module->dataFinished();
-    }
-}
-
 
 /********************************************************************
  * AbstractAnalysisData
@@ -241,12 +126,6 @@ AbstractAnalysisData::columnCount() const
     return columnCount(0);
 }
 
-int
-AbstractAnalysisData::frameCount() const
-{
-    return impl_->nframes_;
-}
-
 
 AnalysisDataFrameRef
 AbstractAnalysisData::tryGetDataFrame(int index) const
@@ -286,19 +165,7 @@ AbstractAnalysisData::requestStorage(int nframes)
 void
 AbstractAnalysisData::addModule(AnalysisDataModulePointer module)
 {
-    impl_->checkModuleProperties(*module);
-
-    if (impl_->bDataStart_)
-    {
-        GMX_RELEASE_ASSERT(!impl_->bInFrame_,
-                           "Cannot add data modules in mid-frame");
-        impl_->presentData(this, module.get());
-    }
-    if (!(module->flags() & AnalysisDataModuleInterface::efAllowMissing))
-    {
-        impl_->bAllowMissing_ = false;
-    }
-    impl_->modules_.push_back(module);
+    impl_->modules_.addModule(this, module);
 }
 
 
@@ -308,11 +175,6 @@ AbstractAnalysisData::addColumnModule(int col, int span,
 {
     GMX_RELEASE_ASSERT(col >= 0 && span >= 1,
                        "Invalid columns specified for a column module");
-    if (impl_->bDataStart_)
-    {
-        GMX_THROW(NotImplementedError("Cannot add column modules after data"));
-    }
-
     boost::shared_ptr<AnalysisDataProxy> proxy(
             new AnalysisDataProxy(col, span, this));
     proxy->addModule(module);
@@ -323,11 +185,7 @@ AbstractAnalysisData::addColumnModule(int col, int span,
 void
 AbstractAnalysisData::applyModule(AnalysisDataModuleInterface *module)
 {
-    impl_->checkModuleProperties(*module);
-    GMX_RELEASE_ASSERT(impl_->bDataStart_ && !impl_->bInData_,
-                       "Data module can only be applied to ready data");
-
-    impl_->presentData(this, module);
+    impl_->modules_.applyModule(this, module);
 }
 
 /*! \cond libapi */
@@ -335,8 +193,8 @@ void
 AbstractAnalysisData::setDataSetCount(int dataSetCount)
 {
     GMX_RELEASE_ASSERT(dataSetCount > 0, "Invalid data column count");
-    GMX_RELEASE_ASSERT(!impl_->bDataStart_,
-                       "Data set count cannot be changed after data has been added");
+    impl_->modules_.dataPropertyAboutToChange(
+            AnalysisDataModuleManager::eMultipleDataSets, dataSetCount > 1);
     impl_->columnCounts_.resize(dataSetCount);
 }
 
@@ -346,121 +204,36 @@ AbstractAnalysisData::setColumnCount(int dataSet, int columnCount)
     GMX_RELEASE_ASSERT(dataSet >= 0 && dataSet < dataSetCount(),
                        "Out of range data set index");
     GMX_RELEASE_ASSERT(columnCount > 0, "Invalid data column count");
-    GMX_RELEASE_ASSERT(!impl_->bDataStart_,
-                       "Data column count cannot be changed after data has been added");
-    impl_->columnCounts_[dataSet] = columnCount;
-}
-
-void
-AbstractAnalysisData::setMultipoint(bool multipoint)
-{
-    GMX_RELEASE_ASSERT(!impl_->bDataStart_,
-                       "Data type cannot be changed after data has been added");
-    impl_->bMultipoint_ = multipoint;
-}
-
-
-/*! \internal
- * This method is not const because the dataStarted() methods of the attached
- * modules can request storage of the data.
- */
-void
-AbstractAnalysisData::notifyDataStart()
-{
-    GMX_RELEASE_ASSERT(!impl_->bDataStart_,
-                       "notifyDataStart() called more than once");
-    for (int d = 0; d < dataSetCount(); ++d)
-    {
-        GMX_RELEASE_ASSERT(columnCount(d) > 0,
-                           "Data column count is not set");
-    }
-    impl_->bDataStart_ = impl_->bInData_ = true;
 
-    Impl::ModuleList::const_iterator i;
-    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
+    bool bMultipleColumns = columnCount > 1;
+    for (int i = 0; i < dataSetCount() && !bMultipleColumns; ++i)
     {
-        impl_->checkModuleProperties(**i);
-        (*i)->dataStarted(this);
-    }
-}
-
-
-void
-AbstractAnalysisData::notifyFrameStart(const AnalysisDataFrameHeader &header) const
-{
-    GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
-    GMX_ASSERT(!impl_->bInFrame_,
-               "notifyFrameStart() called while inside a frame");
-    GMX_ASSERT(header.index() == impl_->nframes_,
-               "Out of order frames");
-    impl_->bInFrame_  = true;
-    impl_->currIndex_ = header.index();
-
-    Impl::ModuleList::const_iterator i;
-    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
-    {
-        (*i)->frameStarted(header);
+        if (i != dataSet && this->columnCount(i) > 1)
+        {
+            bMultipleColumns = true;
+        }
     }
+    impl_->modules_.dataPropertyAboutToChange(
+            AnalysisDataModuleManager::eMultipleColumns, bMultipleColumns);
+    impl_->columnCounts_[dataSet] = columnCount;
 }
 
-
 void
-AbstractAnalysisData::notifyPointsAdd(const AnalysisDataPointSetRef &points) const
+AbstractAnalysisData::setMultipoint(bool bMultipoint)
 {
-    GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
-    GMX_ASSERT(impl_->bInFrame_, "notifyFrameStart() not called");
-    GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
-               "Invalid columns");
-    GMX_ASSERT(points.frameIndex() == impl_->currIndex_,
-               "Points do not correspond to current frame");
-    if (!impl_->bAllowMissing_ && !points.allPresent())
-    {
-        GMX_THROW(APIError("Missing data not supported by a module"));
-    }
-
-    Impl::ModuleList::const_iterator i;
-    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
-    {
-        (*i)->pointsAdded(points);
-    }
+    impl_->modules_.dataPropertyAboutToChange(
+            AnalysisDataModuleManager::eMultipoint, bMultipoint);
+    impl_->bMultipoint_ = bMultipoint;
 }
 
-
-void
-AbstractAnalysisData::notifyFrameFinish(const AnalysisDataFrameHeader &header)
+AnalysisDataModuleManager &AbstractAnalysisData::moduleManager()
 {
-    GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
-    GMX_ASSERT(impl_->bInFrame_, "notifyFrameStart() not called");
-    GMX_ASSERT(header.index() == impl_->currIndex_,
-               "Header does not correspond to current frame");
-    impl_->bInFrame_  = false;
-    impl_->currIndex_ = -1;
-
-    // Increment the counter before notifications to allow frame access from
-    // modules.
-    ++impl_->nframes_;
-
-    Impl::ModuleList::const_iterator i;
-    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
-    {
-        (*i)->frameFinished(header);
-    }
+    return impl_->modules_;
 }
 
-
-void
-AbstractAnalysisData::notifyDataFinish() const
+const AnalysisDataModuleManager &AbstractAnalysisData::moduleManager() const
 {
-    GMX_RELEASE_ASSERT(impl_->bInData_, "notifyDataStart() not called");
-    GMX_RELEASE_ASSERT(!impl_->bInFrame_,
-                       "notifyDataFinish() called while inside a frame");
-    impl_->bInData_ = false;
-
-    Impl::ModuleList::const_iterator i;
-    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
-    {
-        (*i)->dataFinished();
-    }
+    return impl_->modules_;
 }
 //! \endcond
 
index 3a9a8de7ecf2c9fc59527a87b7decce399a4fe63..55684db2570120b91a13a35dfdafc99bfdb73ac9 100644 (file)
@@ -53,10 +53,10 @@ namespace gmx
 {
 
 class AnalysisDataModuleInterface;
+class AnalysisDataModuleManager;
 class AnalysisDataFrameHeader;
 class AnalysisDataFrameRef;
 class AnalysisDataPointSetRef;
-class AnalysisDataStorage;
 
 //! Smart pointer for managing a generic analysis data module.
 typedef boost::shared_ptr<AnalysisDataModuleInterface> AnalysisDataModulePointer;
@@ -78,14 +78,16 @@ typedef boost::shared_ptr<AnalysisDataModuleInterface> AnalysisDataModulePointer
  * \if libapi
  * This class also provides protected methods for use in derived classes.
  * The properties returned by isMultipoint(), dataSetCount(), and columnCount()
- * must be set using setMultipoint(), setDataSetCount(), and setColumnCount(),
- * and notify*() methods must be used to report when data becomes available for
+ * must be set using setMultipoint(), setDataSetCount(), and setColumnCount().
+ * notify*() methods in the AnalysisDataModuleManager returned by
+ * moduleManager() must be used to report when data becomes available for
  * modules to process it.
- * There are also two protected pure virtual methods that need to be
- * implemented to provide access to stored data: requestStorageInternal() and
- * tryGetDataFrameInternal().
+ * There are also three pure virtual methods that need to be implemented to
+ * provide access to stored data: one public (frameCount()) and two protected
+ * ones (requestStorageInternal() and tryGetDataFrameInternal()).
  *
- * It is up to subclasses to ensure that the protected methods are called in a
+ * It is up to subclasses to ensure that the virtual methods and the
+ * notifications in AnalysisDataModuleManager are called in a
  * correct sequence (the methods will assert in most incorrect use cases), and
  * that the data provided through the public interface matches that passed to
  * the modules with the notify methods.
@@ -196,8 +198,16 @@ class AbstractAnalysisData
          * all of these frames.
          *
          * Does not throw.
+         *
+         * \if libapi
+         * Derived classes should implement this to return the number of
+         * frames.  The frame count should not be incremented before
+         * tryGetDataFrameInternal() can return the new frame.
+         * The frame count must be incremented before
+         * AnalysisDataModuleManager::notifyFrameFinish() is called.
+         * \endif
          */
-        int frameCount() const;
+        virtual int frameCount() const = 0;
         /*! \brief
          * Access stored data.
          *
@@ -247,12 +257,15 @@ class AbstractAnalysisData
          * Adds a module to process the data.
          *
          * \param     module  Module to add.
+         * \throws    std::bad_alloc if out of memory.
          * \throws    APIError if
          *      - \p module is not compatible with the data object
          *      - data has already been added to the data object and everything
          *        is not available through getDataFrame().
+         * \throws    unspecified Any exception thrown by \p module in its
+         *      notification methods (if data has been added).
          *
-         * If data has already been added to the module, the new module
+         * If data has already been added to the data, the new module
          * immediately processes all existing data.  APIError is thrown
          * if all data is not available through getDataFrame().
          *
@@ -269,7 +282,8 @@ class AbstractAnalysisData
          * \param[in] col     First column.
          * \param[in] span    Number of columns.
          * \param     module  Module to add.
-         * \throws    APIError in same situations as addModule().
+         *
+         * Throws in the same situations as addModule().
          *
          * Currently, all data sets are filtered using the same column mask.
          *
@@ -291,6 +305,8 @@ class AbstractAnalysisData
          *
          * \param     module  Module to apply.
          * \throws    APIError in same situations as addModule().
+         * \throws    unspecified Any exception thrown by \p module in its
+         *      notification methods.
          *
          * This function works as addModule(), except that it does not keep a
          * reference to \p module within the data object after it returns.
@@ -319,14 +335,16 @@ class AbstractAnalysisData
          * Sets the number of data sets.
          *
          * \param[in] dataSetCount  Number of data sets (must be > 0).
+         * \throws    std::bad_alloc if out of memory.
+         * \throws    APIError if modules have been added that are not
+         *      compatible with the new data set count.
          *
-         * It not called, the data object has a single data set.
-         * Can be called only before notifyDataStart().
+         * It not called, the data object has a single data set.  Can be called
+         * only before AnalysisDataModuleManager::notifyDataStart().
          * Multiple calls are allowed before that point; the last call takes
          * effect.
          *
-         * Does not throw, but this may change with the todo item in
-         * setColumnCount().
+         * Strong exception safety.
          *
          * \see dataSetCount()
          */
@@ -336,18 +354,16 @@ class AbstractAnalysisData
          *
          * \param[in] dataSet      Zero-based index of the data set.
          * \param[in] columnCount  Number of columns in \p dataSet (must be > 0).
+         * \throws    APIError if modules have been added that are not
+         *      compatible with the new column count.
          *
-         * Must be called at least once before notifyDataStart() for each data
-         * set.
-         * Can be called only before notifyDataStart().
+         * Must be called at least once for each data set before
+         * AnalysisDataModuleManager::notifyDataStart().  Can be called only
+         * before AnalysisDataModuleManager::notifyDataStart().
          * Multiple calls are allowed before that point; the last call takes
          * effect.
          *
-         * Does not throw, but this may change with the below todo item.
-         *
-         * \todo
-         * Consider whether the call should check the modules that have already
-         * been added (currently it is only done in notifyDataStart()).
+         * Strong exception safety.
          *
          * \see columnCount()
          */
@@ -355,20 +371,21 @@ class AbstractAnalysisData
         /*! \brief
          * Sets whether the data has multiple points per column in a frame.
          *
-         * \param[in] multipoint  Whether multiple points per column are
+         * \param[in] bMultipoint  Whether multiple points per column are
          *     possible.
+         * \throws    APIError if modules have been added that are not
+         *      compatible with the new setting.
          *
-         * If not called, only a single point per column is allowed.
-         * Can be called only before notifyDataStart().
+         * If not called, only a single point per column is allowed.  Can be
+         * called only before AnalysisDataModuleManager::notifyDataStart().
          * Multiple calls are allowed before that point; the last call takes
          * effect.
          *
-         * Does not throw, but this may change with the todo item in
-         * setColumnCount().
+         * Strong exception safety.
          *
          * \see isMultipoint()
          */
-        void setMultipoint(bool multipoint);
+        void setMultipoint(bool bMultipoint);
 
         /*! \brief
          * Implements access to data frames.
@@ -412,82 +429,16 @@ class AbstractAnalysisData
          */
         virtual bool requestStorageInternal(int nframes) = 0;
 
-        /*! \brief
-         * Notifies attached modules of the start of data.
-         *
-         * \throws    APIError if any attached data module is not compatible.
-         * \throws    unspecified Any exception thrown by attached data modules
-         *      in AnalysisDataModuleInterface::dataStarted().
-         *
-         * Should be called once, after data properties have been set with
-         * setColumnCount() and isMultipoint(), and before any of the
-         * notification functions.  The derived class should prepare for
-         * requestStorage() calls from the attached modules.
-         */
-        void notifyDataStart();
-        /*! \brief
-         * Notifies attached modules of the start of a frame.
-         *
-         * \param[in] header  Header information for the frame that is starting.
-         * \throws    unspecified Any exception thrown by attached data modules
-         *      in AnalysisDataModuleInterface::frameStarted().
-         *
-         * Should be called once for each frame, before notifyPointsAdd() calls
-         * for that frame.
-         */
-        void notifyFrameStart(const AnalysisDataFrameHeader &header) const;
-        /*! \brief
-         * Notifies attached modules of the addition of points to the
-         * current frame.
-         *
-         * \param[in] points  Set of points added (also provides access to
-         *      frame-level data).
-         * \throws    APIError if any attached data module is not compatible.
-         * \throws    unspecified Any exception thrown by attached data modules
-         *      in AnalysisDataModuleInterface::pointsAdded().
-         *
-         * Can be called zero or more times for each frame.
-         * The caller should ensure that any column occurs at most once in the
-         * calls, unless the data is multipoint.
-         * For efficiency reasons, calls to this method should be aggregated
-         * whenever possible, i.e., it's better to handle multiple columns or
-         * even the whole frame in a single call rather than calling the method
-         * for each column separately.
-         */
-        void notifyPointsAdd(const AnalysisDataPointSetRef &points) const;
-        /*! \brief
-         * Notifies attached modules of the end of a frame.
-         *
-         * \param[in] header  Header information for the frame that is ending.
-         * \throws    unspecified Any exception thrown by attached data modules
-         *      in AnalysisDataModuleInterface::frameFinished().
-         *
-         * Should be called once for each call of notifyFrameStart(), after any
-         * notifyPointsAdd() calls for the frame.
-         * \p header should be identical to that used in the corresponding
-         * notifyFrameStart() call.
-         */
-        void notifyFrameFinish(const AnalysisDataFrameHeader &header);
-        /*! \brief
-         * Notifies attached modules of the end of data.
-         *
-         * \throws    unspecified Any exception thrown by attached data modules
-         *      in AnalysisDataModuleInterface::dataFinished().
-         *
-         * Should be called once, after all the other notification calls.
-         */
-        void notifyDataFinish() const;
+        //! Returns the module manager to use for calling notification methods.
+        AnalysisDataModuleManager       &moduleManager();
+        //! Returns the module manager to use for calling notification methods.
+        const AnalysisDataModuleManager &moduleManager() const;
         //! \endcond
 
     private:
         class Impl;
 
         PrivateImplPointer<Impl> impl_;
-
-        /*! \brief
-         * Needed to provide access to notification methods.
-         */
-        friend class AnalysisDataStorage;
 };
 
 } // namespace gmx
index 105a7b59fe1cb23b992e72388145447bc8ba0bd5..c679a1fce6fa542f2543ba6ddb8a9feb92a0aaf2 100644 (file)
@@ -151,6 +151,13 @@ AnalysisData::setMultipoint(bool bMultipoint)
 }
 
 
+int
+AnalysisData::frameCount() const
+{
+    return impl_->storage_.frameCount();
+}
+
+
 AnalysisDataHandle
 AnalysisData::startData(const AnalysisDataParallelOptions &opt)
 {
@@ -158,9 +165,8 @@ AnalysisData::startData(const AnalysisDataParallelOptions &opt)
                        "Too many calls to startData() compared to provided options");
     if (impl_->handles_.empty())
     {
-        notifyDataStart();
         impl_->storage_.setParallelOptions(opt);
-        impl_->storage_.startDataStorage(this);
+        impl_->storage_.startDataStorage(this, &moduleManager());
     }
 
     Impl::HandlePointer handle(new internal::AnalysisDataHandleImpl(this));
@@ -188,7 +194,7 @@ AnalysisData::finishData(AnalysisDataHandle handle)
 
     if (impl_->handles_.empty())
     {
-        notifyDataFinish();
+        impl_->storage_.finishDataStorage();
     }
 }
 
index 451cee4a598b74e37def4d65d150258c7ce08e1e..fbd72a5ee6b6e9ebf0a8d2c7868789329bae625d 100644 (file)
@@ -99,13 +99,13 @@ class AnalysisData : public AbstractAnalysisData
          * Sets the number of data sets.
          *
          * \param[in] dataSetCount  Number of data sets (must be > 0).
+         * \throws    std::bad_alloc if out of memory.
+         * \throws    APIError if modules have been added that are not
+         *      compatible with the new data set count.
          *
          * Must not be called after startData() has been called.
          * If not called, a single data set is assumed.
          * If called multiple times, the last call takes effect.
-         *
-         * Does not currently throw, but this may change for the case that
-         * modules have already been added.
          */
         void setDataSetCount(int dataSetCount);
         /*! \brief
@@ -113,13 +113,12 @@ class AnalysisData : public AbstractAnalysisData
          *
          * \param[in] dataSet      Zero-based data set index.
          * \param[in] columnCount  Number of columns in the data (must be > 0).
+         * \throws    APIError if modules have been added that are not
+         *      compatible with the new column count.
          *
          * Must be called before startData() for each data set.
          * Must not be called after startData() has been called.
          * If called multiple times for a data set, the last call takes effect.
-         *
-         * Does not currently throw, but this may change for the case that
-         * modules have already been added.
          */
         void setColumnCount(int dataSet, int columnCount);
         /*! \brief
@@ -127,18 +126,19 @@ class AnalysisData : public AbstractAnalysisData
          *
          * \param[in] bMultipoint  Whether the data will allow multiple points
          *      per column within a single frame.
+         * \throws    APIError if modules have been added that are not
+         *      compatible with the new setting.
          *
          * If this method is not called, the data is not multipoint.
          *
          * Must not be called after startData() has been called.
          *
-         * Does not currently throw, but this may change for the case that
-         * modules have already been added.
-         *
          * \see isMultipoint()
          */
         void setMultipoint(bool bMultipoint);
 
+        virtual int frameCount() const;
+
         /*! \brief
          * Create a handle for adding data.
          *
index f8eca8ccaf119288d22348792b6c868b9a20c57c..fadfb33a379c3900370081ebf44ef4589821febe 100644 (file)
@@ -44,6 +44,7 @@
 #include <algorithm>
 
 #include "gromacs/analysisdata/dataframe.h"
+#include "gromacs/analysisdata/datamodulemanager.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/gmxassert.h"
 
@@ -139,19 +140,20 @@ AbstractAnalysisArrayData::valuesReady()
     bReady_ = true;
 
     std::vector<AnalysisDataValue>::const_iterator valueIter = value_.begin();
-    notifyDataStart();
+    AnalysisDataModuleManager                     &modules   = moduleManager();
+    modules.notifyDataStart(this);
     for (int i = 0; i < rowCount(); ++i, valueIter += columnCount())
     {
         AnalysisDataFrameHeader header(i, xvalue(i), 0);
-        notifyFrameStart(header);
-        notifyPointsAdd(
+        modules.notifyFrameStart(header);
+        modules.notifyPointsAdd(
                 AnalysisDataPointSetRef(
                         header, pointSetInfo_,
                         AnalysisDataValuesRef(valueIter,
                                               valueIter + columnCount())));
-        notifyFrameFinish(header);
+        modules.notifyFrameFinish(header);
     }
-    notifyDataFinish();
+    modules.notifyDataFinish();
 }
 
 
index f4f66096f4ccbdfbd7a22c45370986f831958660..b8dbb85f415580754262c52aa4263ca313fa9811 100644 (file)
@@ -76,6 +76,11 @@ class AbstractAnalysisArrayData : public AbstractAnalysisData
     public:
         virtual ~AbstractAnalysisArrayData();
 
+        virtual int frameCount() const
+        {
+            return bReady_ ? rowCount_ : 0;
+        }
+
         /*! \brief
          * Returns the number of rows in the data array.
          *
index 9ca0f7b3e85fad3f1199b43aa3e29b975b914b67..e6a0b5ae9544307f4e7569083be15b778770218f 100644 (file)
@@ -80,7 +80,8 @@ class AnalysisDataModuleInterface
         /*! \brief
          * Possible flags for flags().
          */
-        enum {
+        enum Flag
+        {
             //! The module can process multipoint data.
             efAllowMultipoint           = 1<<0,
             //! The module does not make sense for non-multipoint data.
diff --git a/src/gromacs/analysisdata/datamodulemanager.cpp b/src/gromacs/analysisdata/datamodulemanager.cpp
new file mode 100644 (file)
index 0000000..c4ecf30
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2010,2011,2012,2013, by the GROMACS development team, led by
+ * David van der Spoel, Berk Hess, Erik Lindahl, and including many
+ * others, as listed in the AUTHORS file in the top-level source
+ * directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements gmx::AnalysisDataModuleManager.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \ingroup module_analysisdata
+ */
+#include "gromacs/analysisdata/datamodulemanager.h"
+
+#include <vector>
+
+#include "gromacs/analysisdata/abstractdata.h"
+#include "gromacs/analysisdata/dataframe.h"
+#include "gromacs/analysisdata/datamodule.h"
+#include "gromacs/utility/exceptions.h"
+#include "gromacs/utility/gmxassert.h"
+
+namespace gmx
+{
+
+/********************************************************************
+ * AnalysisDataModuleManager::Impl
+ */
+
+/*! \internal \brief
+ * Private implementation class for AnalysisDataModuleManager.
+ *
+ * \ingroup module_analysisdata
+ */
+class AnalysisDataModuleManager::Impl
+{
+    public:
+        //! Shorthand for list of modules added to the data.
+        typedef std::vector<AnalysisDataModulePointer> ModuleList;
+
+        //! Describes the current state of the notification methods.
+        enum State
+        {
+            eNotStarted, //!< Initial state (nothing called).
+            eInData,     //!< notifyDataStart() called, no frame in progress.
+            eInFrame,    //!< notifyFrameStart() called, but notifyFrameFinish() not.
+            eFinished    //!< notifyDataFinish() called.
+        };
+
+        Impl();
+
+        /*! \brief
+         * Checks whether a module is compatible with a given data property.
+         *
+         * \param[in] module   Module to check.
+         * \param[in] property Property to check.
+         * \param[in] bSet     Value of the property to check against.
+         * \throws    APIError if \p module is not compatible with the data.
+         */
+        void checkModuleProperty(const AnalysisDataModuleInterface &module,
+                                 DataProperty property, bool bSet) const;
+        /*! \brief
+         * Checks whether a module is compatible with the data properties.
+         *
+         * \param[in] module Module to check.
+         * \throws    APIError if \p module is not compatible with the data.
+         *
+         * Does not currently check the actual data (e.g., missing values), but
+         * only the dimensionality and other preset properties of the data.
+         */
+        void checkModuleProperties(const AnalysisDataModuleInterface &module) const;
+
+        /*! \brief
+         * Present data already added to the data object to a module.
+         *
+         * \param[in] data   Data object to read data from.
+         * \param[in] module Module to present the data to.
+         * \throws    APIError if \p module is not compatible with the data.
+         * \throws    APIError if all data is not available through
+         *      getDataFrame().
+         * \throws    unspecified Any exception thrown by \p module in its data
+         *      notification methods.
+         *
+         * Uses getDataFrame() in \p data to access all data in the object, and
+         * calls the notification functions in \p module as if the module had
+         * been registered to the data object when the data was added.
+         */
+        void presentData(AbstractAnalysisData        *data,
+                         AnalysisDataModuleInterface *module);
+
+        //! List of modules added to the data.
+        ModuleList              modules_;
+        //! Properties of the owning data for module checking.
+        bool                    bDataProperty_[eDataPropertyNR];
+        //! true if all modules support missing data.
+        bool                    bAllowMissing_;
+
+        /*! \brief
+         * Current state of the notification methods.
+         *
+         * This is used together with \a currIndex_ for sanity checks on the
+         * input data; invalid call sequences trigger asserts.
+         * The state of these variables does not otherwise affect the behavior
+         * of this class; this is the reason they can be changed in const
+         * methods.
+         */
+        //! Whether notifyDataStart() has been called.
+        mutable State           state_;
+        //! Index of currently active frame or the next frame if not in frame.
+        mutable int             currIndex_;
+};
+
+AnalysisDataModuleManager::Impl::Impl()
+    : bAllowMissing_(true), state_(eNotStarted), currIndex_(0)
+{
+    // This must be in sync with how AbstractAnalysisData is actually
+    // initialized.
+    for (int i = 0; i < eDataPropertyNR; ++i)
+    {
+        bDataProperty_[i] = false;
+    }
+}
+
+void
+AnalysisDataModuleManager::Impl::checkModuleProperty(
+        const AnalysisDataModuleInterface &module,
+        DataProperty property, bool bSet) const
+{
+    bool      bOk   = true;
+    const int flags = module.flags();
+    switch (property)
+    {
+        case eMultipleDataSets:
+            if (bSet && !(flags & AnalysisDataModuleInterface::efAllowMultipleDataSets))
+            {
+                bOk = false;
+            }
+            break;
+        case eMultipleColumns:
+            if (bSet && !(flags & AnalysisDataModuleInterface::efAllowMulticolumn))
+            {
+                bOk = false;
+            }
+            break;
+        case eMultipoint:
+            if ((bSet && !(flags & AnalysisDataModuleInterface::efAllowMultipoint))
+                || (!bSet && (flags & AnalysisDataModuleInterface::efOnlyMultipoint)))
+            {
+                bOk = false;
+            }
+            break;
+        default:
+            GMX_RELEASE_ASSERT(false, "Invalid data property enumeration");
+    }
+    if (!bOk)
+    {
+        GMX_THROW(APIError("Data module not compatible with data object properties"));
+    }
+}
+
+void
+AnalysisDataModuleManager::Impl::checkModuleProperties(
+        const AnalysisDataModuleInterface &module) const
+{
+    for (int i = 0; i < eDataPropertyNR; ++i)
+    {
+        checkModuleProperty(module, static_cast<DataProperty>(i), bDataProperty_[i]);
+    }
+}
+
+void
+AnalysisDataModuleManager::Impl::presentData(AbstractAnalysisData        *data,
+                                             AnalysisDataModuleInterface *module)
+{
+    if (state_ == eNotStarted)
+    {
+        return;
+    }
+    GMX_RELEASE_ASSERT(state_ != eInFrame,
+                       "Cannot apply a modules in mid-frame");
+    module->dataStarted(data);
+    const bool bCheckMissing = bAllowMissing_
+        && !(module->flags() & AnalysisDataModuleInterface::efAllowMissing);
+    for (int i = 0; i < data->frameCount(); ++i)
+    {
+        AnalysisDataFrameRef frame = data->getDataFrame(i);
+        GMX_RELEASE_ASSERT(frame.isValid(), "Invalid data frame returned");
+        // TODO: Check all frames before doing anything for slightly better
+        // exception behavior.
+        if (bCheckMissing && !frame.allPresent())
+        {
+            GMX_THROW(APIError("Missing data not supported by a module"));
+        }
+        module->frameStarted(frame.header());
+        for (int j = 0; j < frame.pointSetCount(); ++j)
+        {
+            module->pointsAdded(frame.pointSet(j));
+        }
+        module->frameFinished(frame.header());
+    }
+    if (state_ == eFinished)
+    {
+        module->dataFinished();
+    }
+}
+
+/********************************************************************
+ * AnalysisDataModuleManager
+ */
+
+AnalysisDataModuleManager::AnalysisDataModuleManager()
+    : impl_(new Impl())
+{
+}
+
+AnalysisDataModuleManager::~AnalysisDataModuleManager()
+{
+}
+
+void
+AnalysisDataModuleManager::dataPropertyAboutToChange(DataProperty property, bool bSet)
+{
+    GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
+                       "Cannot change data properties after data has been started");
+    if (impl_->bDataProperty_[property] != bSet)
+    {
+        Impl::ModuleList::const_iterator i;
+        for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
+        {
+            impl_->checkModuleProperty(**i, property, bSet);
+        }
+        impl_->bDataProperty_[property] = bSet;
+    }
+}
+
+void
+AnalysisDataModuleManager::addModule(AbstractAnalysisData      *data,
+                                     AnalysisDataModulePointer  module)
+{
+    impl_->checkModuleProperties(*module);
+    GMX_RELEASE_ASSERT(impl_->state_ != Impl::eInFrame,
+                       "Cannot add a data module in mid-frame");
+    impl_->presentData(data, module.get());
+
+    if (!(module->flags() & AnalysisDataModuleInterface::efAllowMissing))
+    {
+        impl_->bAllowMissing_ = false;
+    }
+    impl_->modules_.push_back(module);
+}
+
+void
+AnalysisDataModuleManager::applyModule(AbstractAnalysisData        *data,
+                                       AnalysisDataModuleInterface *module)
+{
+    impl_->checkModuleProperties(*module);
+    GMX_RELEASE_ASSERT(impl_->state_ == Impl::eFinished,
+                       "Data module can only be applied to ready data");
+    impl_->presentData(data, module);
+}
+
+
+void
+AnalysisDataModuleManager::notifyDataStart(AbstractAnalysisData *data) const
+{
+    GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
+                       "notifyDataStart() called more than once");
+    for (int d = 0; d < data->dataSetCount(); ++d)
+    {
+        GMX_RELEASE_ASSERT(data->columnCount(d) > 0,
+                           "Data column count is not set");
+    }
+    impl_->state_ = Impl::eInData;
+
+    Impl::ModuleList::const_iterator i;
+    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
+    {
+        // This should not fail, since addModule() and
+        // dataPropertyAboutToChange() already do the checks, but kept here to
+        // catch potential bugs (perhaps it would be best to assert on failure).
+        impl_->checkModuleProperties(**i);
+        (*i)->dataStarted(data);
+    }
+}
+
+
+void
+AnalysisDataModuleManager::notifyFrameStart(const AnalysisDataFrameHeader &header) const
+{
+    GMX_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
+    GMX_ASSERT(header.index() == impl_->currIndex_, "Out of order frames");
+    impl_->state_     = Impl::eInFrame;
+
+    Impl::ModuleList::const_iterator i;
+    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
+    {
+        (*i)->frameStarted(header);
+    }
+}
+
+
+void
+AnalysisDataModuleManager::notifyPointsAdd(const AnalysisDataPointSetRef &points) const
+{
+    GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
+    // TODO: Add checks for column spans (requires passing the information
+    // about the column counts from somewhere).
+    //GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
+    //           "Invalid columns");
+    GMX_ASSERT(points.frameIndex() == impl_->currIndex_,
+               "Points do not correspond to current frame");
+    if (!impl_->bAllowMissing_ && !points.allPresent())
+    {
+        GMX_THROW(APIError("Missing data not supported by a module"));
+    }
+
+    Impl::ModuleList::const_iterator i;
+    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
+    {
+        (*i)->pointsAdded(points);
+    }
+}
+
+
+void
+AnalysisDataModuleManager::notifyFrameFinish(const AnalysisDataFrameHeader &header) const
+{
+    GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
+    GMX_ASSERT(header.index() == impl_->currIndex_,
+               "Header does not correspond to current frame");
+    // TODO: Add a check for the frame count in the source data including this
+    // frame.
+    impl_->state_ = Impl::eInData;
+    ++impl_->currIndex_;
+
+    Impl::ModuleList::const_iterator i;
+    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
+    {
+        (*i)->frameFinished(header);
+    }
+}
+
+
+void
+AnalysisDataModuleManager::notifyDataFinish() const
+{
+    GMX_RELEASE_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
+    impl_->state_ = Impl::eFinished;
+
+    Impl::ModuleList::const_iterator i;
+    for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
+    {
+        (*i)->dataFinished();
+    }
+}
+
+} // namespace gmx
diff --git a/src/gromacs/analysisdata/datamodulemanager.h b/src/gromacs/analysisdata/datamodulemanager.h
new file mode 100644 (file)
index 0000000..67f5a93
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2010,2011,2012,2013, by the GROMACS development team, led by
+ * David van der Spoel, Berk Hess, Erik Lindahl, and including many
+ * others, as listed in the AUTHORS file in the top-level source
+ * directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \libinternal \file
+ * \brief
+ * Declares gmx::AnalysisDataModuleManager.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \inlibraryapi
+ * \ingroup module_analysisdata
+ */
+#ifndef GMX_ANALYSISDATA_DATAMODULEMANAGER_H
+#define GMX_ANALYSISDATA_DATAMODULEMANAGER_H
+
+#include "abstractdata.h"
+
+#include "../utility/common.h"
+
+namespace gmx
+{
+
+/*! \libinternal \brief
+ * Encapsulates handling of data modules attached to AbstractAnalysisData.
+ *
+ * \inlibraryapi
+ * \ingroup module_analysisdata
+ */
+class AnalysisDataModuleManager
+{
+    public:
+        /*! \brief
+         * Identifies data properties to check with data modules.
+         *
+         * \see AnalysisDataModuleInterface::Flag
+         */
+        enum DataProperty
+        {
+            eMultipleDataSets, //!< Data has multiple data sets.
+            eMultipleColumns,  //!< Data has multiple columns.
+            eMultipoint,       //!< Data is multipoint.
+            eDataPropertyNR    //!< Number of properties; for internal use only.
+        };
+
+        AnalysisDataModuleManager();
+        ~AnalysisDataModuleManager();
+
+        /*! \brief
+         * Allows the manager to check modules for compatibility with the data.
+         *
+         * \throws  APIError if any data module already added is not compatible
+         *      with the new setting.
+         *
+         * Does two things: checks any modules already attached to the data and
+         * throws if any of them is not compatible, and stores the property
+         * to check modules attached in the future.
+         *
+         * Strong exception safety.
+         */
+        void dataPropertyAboutToChange(DataProperty property, bool bSet);
+
+        /*! \brief
+         * Adds a module to process the data.
+         *
+         * \param     data    Data object to add the module to.
+         * \param     module  Module to add.
+         * \throws    std::bad_alloc if out of memory.
+         * \throws    APIError if
+         *      - \p module is not compatible with the data object
+         *      - data has already been added to the data object and everything
+         *        is not available through getDataFrame().
+         * \throws    unspecified Any exception thrown by \p module in its
+         *      notification methods (if data has been added).
+         *
+         * \see AbstractAnalysisData::addModule()
+         */
+        void addModule(AbstractAnalysisData      *data,
+                       AnalysisDataModulePointer  module);
+        /*! \brief
+         * Applies a module to process data that is ready.
+         *
+         * \param     data    Data object to apply the module to.
+         * \param     module  Module to apply.
+         * \throws    APIError in same situations as addModule().
+         * \throws    unspecified Any exception thrown by \p module in its
+         *      notification methods.
+         *
+         * \see AbstractAnalysisData::applyModule()
+         */
+        void applyModule(AbstractAnalysisData        *data,
+                         AnalysisDataModuleInterface *module);
+
+        /*! \brief
+         * Notifies attached modules of the start of data.
+         *
+         * \param   data  Data object that is starting.
+         * \throws  APIError if any attached data module is not compatible.
+         * \throws  unspecified Any exception thrown by attached data modules
+         *      in AnalysisDataModuleInterface::dataStarted().
+         *
+         * Should be called once, after data properties have been set with
+         * the methods in AbstractAnalysisData, and before any other
+         * notification methods.
+         * The caller should be prepared for requestStorage() calls to \p data
+         * from the attached modules.
+         *
+         * \p data should typically be \c this when calling from a class
+         * derived from AbstractAnalysisData.
+         */
+        void notifyDataStart(AbstractAnalysisData *data) const;
+        /*! \brief
+         * Notifies attached modules of the start of a frame.
+         *
+         * \param[in] header  Header information for the frame that is starting.
+         * \throws    unspecified Any exception thrown by attached data modules
+         *      in AnalysisDataModuleInterface::frameStarted().
+         *
+         * Should be called once for each frame, before notifyPointsAdd() calls
+         * for that frame.
+         */
+        void notifyFrameStart(const AnalysisDataFrameHeader &header) const;
+        /*! \brief
+         * Notifies attached modules of the addition of points to the
+         * current frame.
+         *
+         * \param[in] points  Set of points added (also provides access to
+         *      frame-level data).
+         * \throws    APIError if any attached data module is not compatible.
+         * \throws    unspecified Any exception thrown by attached data modules
+         *      in AnalysisDataModuleInterface::pointsAdded().
+         *
+         * Can be called zero or more times for each frame.
+         * The caller should ensure that any column occurs at most once in the
+         * calls, unless the data is multipoint.
+         * For efficiency reasons, calls to this method should be aggregated
+         * whenever possible, i.e., it's better to handle multiple columns or
+         * even the whole frame in a single call rather than calling the method
+         * for each column separately.
+         */
+        void notifyPointsAdd(const AnalysisDataPointSetRef &points) const;
+        /*! \brief
+         * Notifies attached modules of the end of a frame.
+         *
+         * \param[in] header  Header information for the frame that is ending.
+         * \throws    unspecified Any exception thrown by attached data modules
+         *      in AnalysisDataModuleInterface::frameFinished().
+         *
+         * Should be called once for each call of notifyFrameStart(), after any
+         * notifyPointsAdd() calls for the frame.
+         * \p header should be identical to that used in the corresponding
+         * notifyFrameStart() call.
+         */
+        void notifyFrameFinish(const AnalysisDataFrameHeader &header) const;
+        /*! \brief
+         * Notifies attached modules of the end of data.
+         *
+         * \throws    unspecified Any exception thrown by attached data modules
+         *      in AnalysisDataModuleInterface::dataFinished().
+         *
+         * Should be called once, after all the other notification calls.
+         */
+        void notifyDataFinish() const;
+
+    private:
+        class Impl;
+
+        PrivateImplPointer<Impl> impl_;
+};
+
+} // namespace gmx
+
+#endif
index 5b015ccc96f9e1cda0ba2666025fc707edb7eaf8..044e523202ad1990fa2bcd1163e384c17af80314 100644 (file)
@@ -42,6 +42,7 @@
 #include "dataproxy.h"
 
 #include "gromacs/analysisdata/dataframe.h"
+#include "gromacs/analysisdata/datamodulemanager.h"
 #include "gromacs/utility/gmxassert.h"
 
 namespace gmx
@@ -57,6 +58,13 @@ AnalysisDataProxy::AnalysisDataProxy(int firstColumn, int columnSpan,
 }
 
 
+int
+AnalysisDataProxy::frameCount() const
+{
+    return source_.frameCount();
+}
+
+
 AnalysisDataFrameRef
 AnalysisDataProxy::tryGetDataFrameInternal(int index) const
 {
@@ -93,14 +101,14 @@ AnalysisDataProxy::dataStarted(AbstractAnalysisData *data)
     {
         setColumnCount(i, columnSpan_);
     }
-    notifyDataStart();
+    moduleManager().notifyDataStart(this);
 }
 
 
 void
 AnalysisDataProxy::frameStarted(const AnalysisDataFrameHeader &frame)
 {
-    notifyFrameStart(frame);
+    moduleManager().notifyFrameStart(frame);
 }
 
 
@@ -110,7 +118,7 @@ AnalysisDataProxy::pointsAdded(const AnalysisDataPointSetRef &points)
     AnalysisDataPointSetRef columns(points, firstColumn_, columnSpan_);
     if (columns.columnCount() > 0)
     {
-        notifyPointsAdd(columns);
+        moduleManager().notifyPointsAdd(columns);
     }
 }
 
@@ -118,14 +126,14 @@ AnalysisDataProxy::pointsAdded(const AnalysisDataPointSetRef &points)
 void
 AnalysisDataProxy::frameFinished(const AnalysisDataFrameHeader &header)
 {
-    notifyFrameFinish(header);
+    moduleManager().notifyFrameFinish(header);
 }
 
 
 void
 AnalysisDataProxy::dataFinished()
 {
-    notifyDataFinish();
+    moduleManager().notifyDataFinish();
 }
 
 } // namespace gmx
index 1f0c3020487f9035c90758082843287a54eddc87..d919248a81746be3a2e357f38de7f7efb8eeb758 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2010,2011,2012, by the GROMACS development team, led by
+ * Copyright (c) 2010,2011,2012,2013, by the GROMACS development team, led by
  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
  * others, as listed in the AUTHORS file in the top-level source
  * directory and at http://www.gromacs.org.
@@ -79,6 +79,8 @@ class AnalysisDataProxy : public AbstractAnalysisData,
         AnalysisDataProxy(int firstColumn, int columnSpan,
                           AbstractAnalysisData *data);
 
+        virtual int frameCount() const;
+
         virtual int flags() const;
 
         virtual void dataStarted(AbstractAnalysisData *data);
index 94d443e9adf72fa688b7088126d798efa0382cb8..431f2b6c767f5305e910db5023899187ef82d900 100644 (file)
@@ -48,6 +48,7 @@
 
 #include "gromacs/analysisdata/abstractdata.h"
 #include "gromacs/analysisdata/dataframe.h"
+#include "gromacs/analysisdata/datamodulemanager.h"
 #include "gromacs/analysisdata/paralleloptions.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/gmxassert.h"
@@ -75,37 +76,33 @@ AnalysisDataParallelOptions::AnalysisDataParallelOptions(int parallelizationFact
 
 
 /********************************************************************
- * AnalysisDataStorage::Impl declaration
+ * AnalysisDataStorageImpl declaration
  */
 
 namespace internal
 {
+
 //! Smart pointer type for managing a storage frame builder.
 typedef gmx_unique_ptr<AnalysisDataStorageFrame>::type
     AnalysisDataFrameBuilderPointer;
-}   // namespace internal
 
 /*! \internal \brief
  * Private implementation class for AnalysisDataStorage.
  *
  * \ingroup module_analysisdata
  */
-class AnalysisDataStorage::Impl
+class AnalysisDataStorageImpl
 {
     public:
-        //! Short-hand for the internal frame data type.
-        typedef internal::AnalysisDataStorageFrameData FrameData;
         //! Smart pointer type for managing a stored frame.
-        typedef gmx_unique_ptr<FrameData>::type FramePointer;
-        //! Short-hand for a smart pointer type to a storage frame builder.
-        typedef internal::AnalysisDataFrameBuilderPointer FrameBuilderPointer;
+        typedef gmx_unique_ptr<AnalysisDataStorageFrameData>::type FramePointer;
 
         //! Shorthand for a list of data frames that are currently stored.
         typedef std::vector<FramePointer> FrameList;
         //! Shorthand for a list of currently unused storage frame builders.
-        typedef std::vector<FrameBuilderPointer> FrameBuilderList;
+        typedef std::vector<AnalysisDataFrameBuilderPointer> FrameBuilderList;
 
-        Impl();
+        AnalysisDataStorageImpl();
 
         //! Returns whether the storage is set to use multipoint data.
         bool isMultipoint() const;
@@ -122,6 +119,8 @@ class AnalysisDataStorage::Impl
         }
         //! Returns the index of the oldest frame that may be currently stored.
         int firstStoredIndex() const;
+        //! Returns the index of the first frame that is not fully notified.
+        int firstUnnotifiedIndex() const { return firstUnnotifiedIndex_; }
         /*! \brief
          * Computes index into \a frames_ for accessing frame \p index.
          *
@@ -163,7 +162,7 @@ class AnalysisDataStorage::Impl
          *
          * \throws std::bad_alloc if out of memory.
          */
-        FrameBuilderPointer getFrameBuilder();
+        AnalysisDataFrameBuilderPointer getFrameBuilder();
 
         /*! \brief
          * Returns whether notifications should be immediately fired.
@@ -177,13 +176,6 @@ class AnalysisDataStorage::Impl
         {
             return isMultipoint() && storageLimit_ == 0 && pendingLimit_ == 1;
         }
-        /*! \brief
-         * Calls notification method in \a data_.
-         *
-         * \throws    unspecified  Any exception thrown by
-         *      AbstractAnalysisData::notifyPointsAdd().
-         */
-        void notifyPointSet(const AnalysisDataPointSetRef &points);
         /*! \brief
          * Calls notification methods for new frames.
          *
@@ -200,8 +192,10 @@ class AnalysisDataStorage::Impl
         void finishFrame(int index);
 
 
-        //! Data object to use for notification calls.
-        AbstractAnalysisData   *data_;
+        //! Parent data object to access data dimensionality etc.
+        const AbstractAnalysisData *data_;
+        //! Manager to use for notification calls.
+        AnalysisDataModuleManager  *modules_;
         /*! \brief
          * Number of past frames that need to be stored.
          *
@@ -243,6 +237,8 @@ class AnalysisDataStorage::Impl
         FrameList               frames_;
         //! Location of oldest frame in \a frames_.
         size_t                  firstFrameLocation_;
+        //! Index of the first frame that is not fully notified.
+        int                     firstUnnotifiedIndex_;
         /*! \brief
          * Currently unused frame builders.
          *
@@ -267,9 +263,6 @@ class AnalysisDataStorage::Impl
  * AnalysisDataStorageFrameImpl declaration
  */
 
-namespace internal
-{
-
 /*! \internal \brief
  * Internal representation for a single stored frame.
  *
@@ -301,8 +294,8 @@ class AnalysisDataStorageFrameData
          * \param     storageImpl  Storage object this frame belongs to.
          * \param[in] index        Zero-based index for the frame.
          */
-        AnalysisDataStorageFrameData(AnalysisDataStorage::Impl *storageImpl,
-                                     int                        index);
+        AnalysisDataStorageFrameData(AnalysisDataStorageImpl *storageImpl,
+                                     int                      index);
 
         //! Whether the frame has been started with startFrame().
         bool isStarted() const { return status_ >= eStarted; }
@@ -317,7 +310,7 @@ class AnalysisDataStorageFrameData
         void markNotified() { status_ = eNotified; }
 
         //! Returns the storage implementation object.
-        AnalysisDataStorage::Impl &storageImpl() const { return storageImpl_; }
+        AnalysisDataStorageImpl &storageImpl() const { return storageImpl_; }
         //! Returns the underlying data object (for data dimensionalities etc.).
         const AbstractAnalysisData &baseData() const { return *storageImpl().data_; }
 
@@ -367,7 +360,7 @@ class AnalysisDataStorageFrameData
 
     private:
         //! Storage object that contains this frame.
-        AnalysisDataStorage::Impl              &storageImpl_;
+        AnalysisDataStorageImpl                &storageImpl_;
         //! Header for the frame.
         AnalysisDataFrameHeader                 header_;
         //! Values for the frame.
@@ -387,21 +380,20 @@ class AnalysisDataStorageFrameData
         GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisDataStorageFrameData);
 };
 
-}   // namespace internal
-
 /********************************************************************
- * AnalysisDataStorage::Impl implementation
+ * AnalysisDataStorageImpl implementation
  */
 
-AnalysisDataStorage::Impl::Impl()
-    : data_(NULL),
-      storageLimit_(0), pendingLimit_(1), firstFrameLocation_(0), nextIndex_(0)
+AnalysisDataStorageImpl::AnalysisDataStorageImpl()
+    : data_(NULL), modules_(NULL),
+      storageLimit_(0), pendingLimit_(1),
+      firstFrameLocation_(0), firstUnnotifiedIndex_(0), nextIndex_(0)
 {
 }
 
 
 bool
-AnalysisDataStorage::Impl::isMultipoint() const
+AnalysisDataStorageImpl::isMultipoint() const
 {
     GMX_ASSERT(data_ != NULL, "isMultipoint() called too early");
     return data_->isMultipoint();
@@ -409,14 +401,14 @@ AnalysisDataStorage::Impl::isMultipoint() const
 
 
 int
-AnalysisDataStorage::Impl::firstStoredIndex() const
+AnalysisDataStorageImpl::firstStoredIndex() const
 {
     return frames_[firstFrameLocation_]->frameIndex();
 }
 
 
 int
-AnalysisDataStorage::Impl::computeStorageLocation(int index) const
+AnalysisDataStorageImpl::computeStorageLocation(int index) const
 {
     if (index < firstStoredIndex() || index >= nextIndex_)
     {
@@ -427,7 +419,7 @@ AnalysisDataStorage::Impl::computeStorageLocation(int index) const
 
 
 size_t
-AnalysisDataStorage::Impl::endStorageLocation() const
+AnalysisDataStorageImpl::endStorageLocation() const
 {
     if (storeAll())
     {
@@ -442,12 +434,12 @@ AnalysisDataStorage::Impl::endStorageLocation() const
 
 
 void
-AnalysisDataStorage::Impl::extendBuffer(size_t newSize)
+AnalysisDataStorageImpl::extendBuffer(size_t newSize)
 {
     frames_.reserve(newSize);
     while (frames_.size() < newSize)
     {
-        frames_.push_back(FramePointer(new FrameData(this, nextIndex_)));
+        frames_.push_back(FramePointer(new AnalysisDataStorageFrameData(this, nextIndex_)));
         ++nextIndex_;
     }
     // The unused frame should not be included in the count.
@@ -459,7 +451,7 @@ AnalysisDataStorage::Impl::extendBuffer(size_t newSize)
 
 
 void
-AnalysisDataStorage::Impl::rotateBuffer()
+AnalysisDataStorageImpl::rotateBuffer()
 {
     GMX_ASSERT(!storeAll(),
                "No need to rotate internal buffer if everything is stored");
@@ -475,57 +467,46 @@ AnalysisDataStorage::Impl::rotateBuffer()
 }
 
 
-internal::AnalysisDataFrameBuilderPointer
-AnalysisDataStorage::Impl::getFrameBuilder()
+AnalysisDataFrameBuilderPointer
+AnalysisDataStorageImpl::getFrameBuilder()
 {
     if (builders_.empty())
     {
-        return FrameBuilderPointer(new AnalysisDataStorageFrame(*data_));
+        return AnalysisDataFrameBuilderPointer(new AnalysisDataStorageFrame(*data_));
     }
-    FrameBuilderPointer builder(move(builders_.back()));
+    AnalysisDataFrameBuilderPointer builder(move(builders_.back()));
     builders_.pop_back();
     return move(builder);
 }
 
 
 void
-AnalysisDataStorage::Impl::notifyPointSet(const AnalysisDataPointSetRef &points)
-{
-    data_->notifyPointsAdd(points);
-}
-
-
-void
-AnalysisDataStorage::Impl::notifyNextFrames(size_t firstLocation)
+AnalysisDataStorageImpl::notifyNextFrames(size_t firstLocation)
 {
-    if (firstLocation != firstFrameLocation_)
+    if (frames_[firstLocation]->frameIndex() != firstUnnotifiedIndex_)
     {
-        // firstLocation can only be zero here if !storeAll() because
-        // firstFrameLocation_ is always zero for storeAll()
-        int prevIndex =
-            (firstLocation == 0 ? frames_.size() - 1 : firstLocation - 1);
-        if (!frames_[prevIndex]->isNotified())
-        {
-            return;
-        }
+        return;
     }
     size_t i   = firstLocation;
     size_t end = endStorageLocation();
     while (i != end)
     {
-        Impl::FrameData &storedFrame = *frames_[i];
+        AnalysisDataStorageFrameData &storedFrame = *frames_[i];
         if (!storedFrame.isFinished())
         {
             break;
         }
         if (!storedFrame.isNotified())
         {
-            data_->notifyFrameStart(storedFrame.header());
+            // Increment before the notifications to make the frame available
+            // in the module callbacks.
+            ++firstUnnotifiedIndex_;
+            modules_->notifyFrameStart(storedFrame.header());
             for (int j = 0; j < storedFrame.pointSetCount(); ++j)
             {
-                data_->notifyPointsAdd(storedFrame.pointSet(j));
+                modules_->notifyPointsAdd(storedFrame.pointSet(j));
             }
-            data_->notifyFrameFinish(storedFrame.header());
+            modules_->notifyFrameFinish(storedFrame.header());
             storedFrame.markNotified();
             if (storedFrame.frameIndex() >= storageLimit_)
             {
@@ -542,11 +523,12 @@ AnalysisDataStorage::Impl::notifyNextFrames(size_t firstLocation)
 
 
 void
-AnalysisDataStorage::Impl::finishFrame(int index)
+AnalysisDataStorageImpl::finishFrame(int index)
 {
-    int                storageIndex = computeStorageLocation(index);
+    const int storageIndex = computeStorageLocation(index);
     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
-    Impl::FrameData   &storedFrame = *frames_[storageIndex];
+
+    AnalysisDataStorageFrameData &storedFrame = *frames_[storageIndex];
     GMX_RELEASE_ASSERT(storedFrame.isStarted(),
                        "finishFrame() called for frame before startFrame()");
     GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
@@ -556,7 +538,8 @@ AnalysisDataStorage::Impl::finishFrame(int index)
     builders_.push_back(storedFrame.finishFrame(isMultipoint()));
     if (shouldNotifyImmediately())
     {
-        data_->notifyFrameFinish(storedFrame.header());
+        ++firstUnnotifiedIndex_;
+        modules_->notifyFrameFinish(storedFrame.header());
         if (storedFrame.frameIndex() >= storageLimit_)
         {
             rotateBuffer();
@@ -573,12 +556,9 @@ AnalysisDataStorage::Impl::finishFrame(int index)
  * AnalysisDataStorageFrame implementation
  */
 
-namespace internal
-{
-
 AnalysisDataStorageFrameData::AnalysisDataStorageFrameData(
-        AnalysisDataStorage::Impl *storageImpl,
-        int                        index)
+        AnalysisDataStorageImpl *storageImpl,
+        int                      index)
     : storageImpl_(*storageImpl), header_(index, 0.0, 0.0), status_(eMissing)
 {
     GMX_RELEASE_ASSERT(storageImpl->data_ != NULL,
@@ -635,7 +615,7 @@ AnalysisDataStorageFrameData::addPointSet(int dataSetIndex, int firstColumn,
     {
         AnalysisDataPointSetInfo pointSetInfo(0, valueCount,
                                               dataSetIndex, firstColumn);
-        storageImpl().notifyPointSet(
+        storageImpl().modules_->notifyPointsAdd(
                 AnalysisDataPointSetRef(header(), pointSetInfo,
                                         AnalysisDataValuesRef(begin, end)));
     }
@@ -803,6 +783,13 @@ AnalysisDataStorage::setParallelOptions(const AnalysisDataParallelOptions &opt)
 }
 
 
+int
+AnalysisDataStorage::frameCount() const
+{
+    return impl_->firstUnnotifiedIndex();
+}
+
+
 AnalysisDataFrameRef
 AnalysisDataStorage::tryGetDataFrame(int index) const
 {
@@ -811,7 +798,8 @@ AnalysisDataStorage::tryGetDataFrame(int index) const
     {
         return AnalysisDataFrameRef();
     }
-    const Impl::FrameData &storedFrame = *impl_->frames_[storageIndex];
+    const internal::AnalysisDataStorageFrameData &storedFrame
+        = *impl_->frames_[storageIndex];
     if (!storedFrame.isAvailable())
     {
         return AnalysisDataFrameRef();
@@ -840,10 +828,13 @@ AnalysisDataStorage::requestStorage(int nframes)
 
 
 void
-AnalysisDataStorage::startDataStorage(AbstractAnalysisData *data)
+AnalysisDataStorage::startDataStorage(AbstractAnalysisData      *data,
+                                      AnalysisDataModuleManager *modules)
 {
+    modules->notifyDataStart(data);
     // Data needs to be set before calling extendBuffer()
-    impl_->data_ = data;
+    impl_->data_    = data;
+    impl_->modules_ = modules;
     if (!impl_->storeAll())
     {
         impl_->extendBuffer(impl_->storageLimit_ + impl_->pendingLimit_ + 1);
@@ -855,7 +846,7 @@ AnalysisDataStorageFrame &
 AnalysisDataStorage::startFrame(const AnalysisDataFrameHeader &header)
 {
     GMX_ASSERT(header.isValid(), "Invalid header");
-    Impl::FrameData *storedFrame;
+    internal::AnalysisDataStorageFrameData *storedFrame;
     if (impl_->storeAll())
     {
         size_t size = header.index() + 1;
@@ -881,7 +872,7 @@ AnalysisDataStorage::startFrame(const AnalysisDataFrameHeader &header)
     storedFrame->startFrame(header, impl_->getFrameBuilder());
     if (impl_->shouldNotifyImmediately())
     {
-        impl_->data_->notifyFrameStart(header);
+        impl_->modules_->notifyFrameStart(header);
     }
     return storedFrame->builder();
 }
@@ -897,9 +888,10 @@ AnalysisDataStorage::startFrame(int index, real x, real dx)
 AnalysisDataStorageFrame &
 AnalysisDataStorage::currentFrame(int index)
 {
-    int                storageIndex = impl_->computeStorageLocation(index);
+    const int storageIndex = impl_->computeStorageLocation(index);
     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
-    Impl::FrameData   &storedFrame = *impl_->frames_[storageIndex];
+
+    internal::AnalysisDataStorageFrameData &storedFrame = *impl_->frames_[storageIndex];
     GMX_RELEASE_ASSERT(storedFrame.isStarted(),
                        "currentFrame() called for frame before startFrame()");
     GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
@@ -916,4 +908,12 @@ AnalysisDataStorage::finishFrame(int index)
     impl_->finishFrame(index);
 }
 
+void
+AnalysisDataStorage::finishDataStorage()
+{
+    // TODO: Check that all frames have been finished etc.
+    impl_->builders_.clear();
+    impl_->modules_->notifyDataFinish();
+}
+
 } // namespace gmx
index 26ab9640b6230d4df629abc2598098a8a964ed9e..f2043e2f9beb3d3c880c73f184890922f50b7671 100644 (file)
@@ -58,12 +58,14 @@ namespace gmx
 class AbstractAnalysisData;
 class AnalysisDataFrameHeader;
 class AnalysisDataFrameRef;
+class AnalysisDataModuleManager;
 class AnalysisDataParallelOptions;
 
 class AnalysisDataStorage;
 
 namespace internal
 {
+class AnalysisDataStorageImpl;
 class AnalysisDataStorageFrameData;
 }   // namespace internal
 
@@ -187,8 +189,8 @@ class AnalysisDataStorageFrame
          *
          * After this method has been called, all values appear as not set.
          *
-         * Calls AbstractAnalysisData::notifyPointsAdd(), and throws any
-         * exception this method throws.
+         * May call AnalysisDataModuleManager::notifyPointsAdd(), and may throw
+         * any exception this method throws.
          */
         void finishPointSet();
         /*! \brief
@@ -198,8 +200,8 @@ class AnalysisDataStorageFrame
          * after the corresponding call.
          * The frame object must not be accessed after the call.
          *
-         * Calls notification methods in AbstractAnalysisData, and throws any
-         * exceptions these methods throw.
+         * Calls notification methods in AnalysisDataModuleManager, and may
+         * throw any exceptions these methods throw.
          */
         void finishFrame();
 
@@ -232,7 +234,7 @@ class AnalysisDataStorageFrame
         bool                                    bPointSetInProgress_;
 
         //! Needed for access to the constructor.
-        friend class AnalysisDataStorage;
+        friend class internal::AnalysisDataStorageImpl;
         //! Needed for managing the frame the object points to.
         friend class internal::AnalysisDataStorageFrameData;
 
@@ -245,16 +247,14 @@ class AnalysisDataStorageFrame
  * This class implements a standard way of storing data to avoid implementing
  * storage in each class derived from AbstractAnalysisData separately.
  * To use this class in a class derived from AbstractAnalysisData, a member
- * variable of this type should be declared and the data storage methods
- * forwarded to tryGetDataFrame() and requestStorage() in that object.
- * Storage properties should be set up, and then startDataStorage() called
- * after calling AbstractAnalysisData::notifyDataStart().
+ * variable of this type should be declared and the pure virtual methods
+ * forwarded to frameCount(), tryGetDataFrame() and requestStorage().
+ * Storage properties should be set up, and then startDataStorage() called.
  * New frames can then be added using startFrame(), currentFrame() and
- * finishFrame() methods.  These methods (and
- * AnalysisDataStorageFrame::finishPointSet()) take the responsibility of
- * calling AbstractAnalysisData::notifyFrameStart(),
- * AbstractAnalysisData::notifyPointsAdd() and
- * AbstractAnalysisData::notifyFrameFinish() appropriately.
+ * finishFrame() methods.  When all frames are ready, finishDataStorage() must
+ * be called.  These methods (and AnalysisDataStorageFrame::finishPointSet())
+ * take the responsibility of calling all the notification methods in
+ * AnalysisDataModuleManager,
  *
  * \todo
  * Proper multi-threaded implementation.
@@ -281,6 +281,19 @@ class AnalysisDataStorage
          */
         void setParallelOptions(const AnalysisDataParallelOptions &opt);
 
+        /*! \brief
+         * Returns the number of ready frames.
+         *
+         * This method is designed such that calls to
+         * AbstractAnalysisData::frameCount() can be directly forwarded to this
+         * method.  See that method for more documentation.
+         *
+         * If this method returns N, this means that the first N frames have
+         * all been finished.
+         *
+         * \see AbstractAnalysisData::frameCount()
+         */
+        int frameCount() const;
         /*! \brief
          * Implements access to data frames.
          *
@@ -308,21 +321,26 @@ class AnalysisDataStorage
         /*! \brief
          * Start storing data.
          *
-         * \param  data  AbstractAnalysisData object containing this storage.
+         * \param[in] data    AbstractAnalysisData object containing this
+         *      storage.
+         * \param     modules Module manager for \p data.
          * \exception std::bad_alloc if storage allocation fails.
          *
-         * Lifetime of \p data must exceed the lifetime of the storage object
+         * Typically called as \c startDataStorage(this, &moduleManager())
+         * from a member of \p data when the data is ready to be started.
+         * The storage object will take responsibility of calling all
+         * module notification methods in AnalysisDataModuleManager using
+         * \p modules.
+         *
+         * Lifetime of \p data and \p modules must exceed the lifetime of the
+         * storage object
          * (typically, the storage object will be a member in \p data).
-         * The storage object will take responsibility of calling
-         * AbstractAnalysisData::notifyFrameStart(),
-         * AbstractAnalysisData::notifyPointsAdd() and
-         * AbstractAnalysisData::notifyFrameFinish() for \p data appropriately.
-         *
-         * AbstractAnalysisData::notifyDataStart() must have been called for
-         * \p data, because that may trigger storage requests from attached
-         * modules.
+         *
+         * Calls AnalysisDataModuleManager::notifyDataStart(), and throws any
+         * exceptions this method throws.
          */
-        void startDataStorage(AbstractAnalysisData *data);
+        void startDataStorage(AbstractAnalysisData      *data,
+                              AnalysisDataModuleManager *modules);
         /*! \brief
          * Starts storing a new frame.
          *
@@ -346,8 +364,8 @@ class AnalysisDataStorage
          * setParallelOptions().
          * Throws APIError if this constraint is violated.
          *
-         * Calls AbstractAnalysisData::notifyDataStarted() in certain cases,
-         * and throws any exceptions this method throws.
+         * Calls AnalysisDataModuleManager::notifyFrameStart() in certain
+         * cases, and throws any exceptions this method throws.
          */
         AnalysisDataStorageFrame &startFrame(const AnalysisDataFrameHeader &header);
         /*! \brief
@@ -380,14 +398,18 @@ class AnalysisDataStorage
          * \see AnalysisDataStorageFrame::finishFrame()
          */
         void finishFrame(int index);
+        /*! \brief
+         * Finishes storing data.
+         *
+         * Calls AnalysisDataModuleManager::notifyDataFinish(), and throws any
+         * exceptions this method throws.
+         */
+        void finishDataStorage();
 
     private:
-        class Impl;
+        typedef internal::AnalysisDataStorageImpl Impl;
 
         PrivateImplPointer<Impl> impl_;
-
-        //! Needed because the frame needs to access the implementation class.
-        friend class internal::AnalysisDataStorageFrameData;
 };
 
 } // namespace gmx
index 9a19fa799665a3dce1602ddc14101ca77a149c3d..4ab7fb7a7e76673e914d67b41e303f56e27b8aae 100644 (file)
@@ -220,6 +220,12 @@ AnalysisDataFrameAverageModule::~AnalysisDataFrameAverageModule()
 {
 }
 
+int
+AnalysisDataFrameAverageModule::frameCount() const
+{
+    return impl_->storage_.frameCount();
+}
+
 int
 AnalysisDataFrameAverageModule::flags() const
 {
@@ -232,8 +238,7 @@ AnalysisDataFrameAverageModule::dataStarted(AbstractAnalysisData *data)
 {
     setColumnCount(0, data->dataSetCount());
     impl_->sampleCount_.resize(data->dataSetCount());
-    notifyDataStart();
-    impl_->storage_.startDataStorage(this);
+    impl_->storage_.startDataStorage(this, &moduleManager());
 }
 
 void
@@ -275,7 +280,7 @@ AnalysisDataFrameAverageModule::frameFinished(const AnalysisDataFrameHeader &hea
 void
 AnalysisDataFrameAverageModule::dataFinished()
 {
-    notifyDataFinish();
+    impl_->storage_.finishDataStorage();
 }
 
 AnalysisDataFrameRef
index e0480999ea22c09cd154cad72646007d09d2e105..4218e754c35995318ba26ad96cbf926f19985f82 100644 (file)
@@ -167,6 +167,8 @@ class AnalysisDataFrameAverageModule : public AbstractAnalysisData,
         AnalysisDataFrameAverageModule();
         virtual ~AnalysisDataFrameAverageModule();
 
+        virtual int frameCount() const;
+
         virtual int flags() const;
 
         virtual void dataStarted(AbstractAnalysisData *data);
index bb13359465685fc02b8f90cff4d5c2b605097784..ed1929daf88ab95e2508c2f3effb1f1d92352ae7 100644 (file)
@@ -45,6 +45,7 @@
 #include "gromacs/legacyheaders/maths.h"
 
 #include "gromacs/analysisdata/dataframe.h"
+#include "gromacs/analysisdata/datamodulemanager.h"
 #include "gromacs/analysisdata/modules/histogram.h"
 #include "gromacs/utility/exceptions.h"
 #include "gromacs/utility/gmxassert.h"
@@ -264,10 +265,10 @@ AnalysisDataDisplacementModule::frameFinished(const AnalysisDataFrameHeader & /*
             _impl->histm->init(histogramFromBins(0, _impl->max_store / _impl->nmax,
                                                  _impl->dt).integerBins());
         }
-        notifyDataStart();
+        moduleManager().notifyDataStart(this);
     }
     AnalysisDataFrameHeader header(_impl->nstored - 2, _impl->t, 0);
-    notifyFrameStart(header);
+    moduleManager().notifyFrameStart(header);
 
     for (i = _impl->ci - _impl->nmax, step = 1;
          step < _impl->nstored && i != _impl->ci;
@@ -292,10 +293,10 @@ AnalysisDataDisplacementModule::frameFinished(const AnalysisDataFrameHeader & /*
             }
             _impl->currValues_.push_back(AnalysisDataValue(dist2));
         }
-        notifyPointsAdd(AnalysisDataPointSetRef(header, _impl->currValues_));
+        moduleManager().notifyPointsAdd(AnalysisDataPointSetRef(header, _impl->currValues_));
     }
 
-    notifyFrameFinish(header);
+    moduleManager().notifyFrameFinish(header);
 }
 
 
@@ -304,7 +305,7 @@ AnalysisDataDisplacementModule::dataFinished()
 {
     if (_impl->nstored >= 2)
     {
-        notifyDataFinish();
+        moduleManager().notifyDataFinish();
     }
 }
 
index 5360430716a577afe4245ad8b814758673fa6523..72572d2d56b4aeb35b37a3d8c37da17bafc7015c 100644 (file)
@@ -616,6 +616,13 @@ AnalysisDataSimpleHistogramModule::settings() const
 }
 
 
+int
+AnalysisDataSimpleHistogramModule::frameCount() const
+{
+    return impl_->storage_.frameCount();
+}
+
+
 int
 AnalysisDataSimpleHistogramModule::flags() const
 {
@@ -633,8 +640,7 @@ AnalysisDataSimpleHistogramModule::dataStarted(AbstractAnalysisData *data)
     {
         setColumnCount(i, settings().binCount());
     }
-    notifyDataStart();
-    impl_->storage_.startDataStorage(this);
+    impl_->storage_.startDataStorage(this, &moduleManager());
 }
 
 
@@ -676,7 +682,7 @@ AnalysisDataSimpleHistogramModule::frameFinished(const AnalysisDataFrameHeader &
 void
 AnalysisDataSimpleHistogramModule::dataFinished()
 {
-    notifyDataFinish();
+    impl_->storage_.finishDataStorage();
 }
 
 
@@ -736,6 +742,13 @@ AnalysisDataWeightedHistogramModule::settings() const
 }
 
 
+int
+AnalysisDataWeightedHistogramModule::frameCount() const
+{
+    return impl_->storage_.frameCount();
+}
+
+
 int
 AnalysisDataWeightedHistogramModule::flags() const
 {
@@ -752,8 +765,7 @@ AnalysisDataWeightedHistogramModule::dataStarted(AbstractAnalysisData *data)
     {
         setColumnCount(i, settings().binCount());
     }
-    notifyDataStart();
-    impl_->storage_.startDataStorage(this);
+    impl_->storage_.startDataStorage(this, &moduleManager());
 }
 
 
@@ -796,7 +808,7 @@ AnalysisDataWeightedHistogramModule::frameFinished(const AnalysisDataFrameHeader
 void
 AnalysisDataWeightedHistogramModule::dataFinished()
 {
-    notifyDataFinish();
+    impl_->storage_.finishDataStorage();
 }
 
 
index 58d18bd42abe8ea8b4ed4befaaeef2e14fd27e43..658935ccdcafff4771e4fe31703718b10ef4997d 100644 (file)
@@ -379,6 +379,8 @@ class AnalysisDataSimpleHistogramModule : public AbstractAnalysisData,
         //! Returns bin properties for the histogram.
         const AnalysisHistogramSettings &settings() const;
 
+        virtual int frameCount() const;
+
         virtual int flags() const;
 
         virtual void dataStarted(AbstractAnalysisData *data);
@@ -434,6 +436,8 @@ class AnalysisDataWeightedHistogramModule : public AbstractAnalysisData,
         //! \copydoc AnalysisDataSimpleHistogramModule::settings()
         const AnalysisHistogramSettings &settings() const;
 
+        virtual int frameCount() const;
+
         virtual int flags() const;
 
         virtual void dataStarted(AbstractAnalysisData *data);