#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
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
return columnCount(0);
}
-int
-AbstractAnalysisData::frameCount() const
-{
- return impl_->nframes_;
-}
-
AnalysisDataFrameRef
AbstractAnalysisData::tryGetDataFrame(int index) const
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);
}
{
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);
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 */
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);
}
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
{
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;
* \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.
* 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.
*
* 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().
*
* \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.
*
*
* \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.
* 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()
*/
*
* \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()
*/
/*! \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.
*/
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
}
+int
+AnalysisData::frameCount() const
+{
+ return impl_->storage_.frameCount();
+}
+
+
AnalysisDataHandle
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));
if (impl_->handles_.empty())
{
- notifyDataFinish();
+ impl_->storage_.finishDataStorage();
}
}
* 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
*
* \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
*
* \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.
*
#include <algorithm>
#include "gromacs/analysisdata/dataframe.h"
+#include "gromacs/analysisdata/datamodulemanager.h"
#include "gromacs/utility/exceptions.h"
#include "gromacs/utility/gmxassert.h"
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();
}
public:
virtual ~AbstractAnalysisArrayData();
+ virtual int frameCount() const
+ {
+ return bReady_ ? rowCount_ : 0;
+ }
+
/*! \brief
* Returns the number of rows in the data array.
*
/*! \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.
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
#include "dataproxy.h"
#include "gromacs/analysisdata/dataframe.h"
+#include "gromacs/analysisdata/datamodulemanager.h"
#include "gromacs/utility/gmxassert.h"
namespace gmx
}
+int
+AnalysisDataProxy::frameCount() const
+{
+ return source_.frameCount();
+}
+
+
AnalysisDataFrameRef
AnalysisDataProxy::tryGetDataFrameInternal(int index) const
{
{
setColumnCount(i, columnSpan_);
}
- notifyDataStart();
+ moduleManager().notifyDataStart(this);
}
void
AnalysisDataProxy::frameStarted(const AnalysisDataFrameHeader &frame)
{
- notifyFrameStart(frame);
+ moduleManager().notifyFrameStart(frame);
}
AnalysisDataPointSetRef columns(points, firstColumn_, columnSpan_);
if (columns.columnCount() > 0)
{
- notifyPointsAdd(columns);
+ moduleManager().notifyPointsAdd(columns);
}
}
void
AnalysisDataProxy::frameFinished(const AnalysisDataFrameHeader &header)
{
- notifyFrameFinish(header);
+ moduleManager().notifyFrameFinish(header);
}
void
AnalysisDataProxy::dataFinished()
{
- notifyDataFinish();
+ moduleManager().notifyDataFinish();
}
} // namespace gmx
/*
* 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.
AnalysisDataProxy(int firstColumn, int columnSpan,
AbstractAnalysisData *data);
+ virtual int frameCount() const;
+
virtual int flags() const;
virtual void dataStarted(AbstractAnalysisData *data);
#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"
/********************************************************************
- * 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;
}
//! 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.
*
*
* \throws std::bad_alloc if out of memory.
*/
- FrameBuilderPointer getFrameBuilder();
+ AnalysisDataFrameBuilderPointer getFrameBuilder();
/*! \brief
* Returns whether notifications should be immediately fired.
{
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.
*
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.
*
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.
*
* AnalysisDataStorageFrameImpl declaration
*/
-namespace internal
-{
-
/*! \internal \brief
* Internal representation for a single stored frame.
*
* \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; }
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_; }
private:
//! Storage object that contains this frame.
- AnalysisDataStorage::Impl &storageImpl_;
+ AnalysisDataStorageImpl &storageImpl_;
//! Header for the frame.
AnalysisDataFrameHeader header_;
//! Values for the frame.
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();
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_)
{
size_t
-AnalysisDataStorage::Impl::endStorageLocation() const
+AnalysisDataStorageImpl::endStorageLocation() const
{
if (storeAll())
{
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.
void
-AnalysisDataStorage::Impl::rotateBuffer()
+AnalysisDataStorageImpl::rotateBuffer()
{
GMX_ASSERT(!storeAll(),
"No need to rotate internal buffer if everything is stored");
}
-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_)
{
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(),
builders_.push_back(storedFrame.finishFrame(isMultipoint()));
if (shouldNotifyImmediately())
{
- data_->notifyFrameFinish(storedFrame.header());
+ ++firstUnnotifiedIndex_;
+ modules_->notifyFrameFinish(storedFrame.header());
if (storedFrame.frameIndex() >= storageLimit_)
{
rotateBuffer();
* 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,
{
AnalysisDataPointSetInfo pointSetInfo(0, valueCount,
dataSetIndex, firstColumn);
- storageImpl().notifyPointSet(
+ storageImpl().modules_->notifyPointsAdd(
AnalysisDataPointSetRef(header(), pointSetInfo,
AnalysisDataValuesRef(begin, end)));
}
}
+int
+AnalysisDataStorage::frameCount() const
+{
+ return impl_->firstUnnotifiedIndex();
+}
+
+
AnalysisDataFrameRef
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();
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);
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;
storedFrame->startFrame(header, impl_->getFrameBuilder());
if (impl_->shouldNotifyImmediately())
{
- impl_->data_->notifyFrameStart(header);
+ impl_->modules_->notifyFrameStart(header);
}
return storedFrame->builder();
}
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(),
impl_->finishFrame(index);
}
+void
+AnalysisDataStorage::finishDataStorage()
+{
+ // TODO: Check that all frames have been finished etc.
+ impl_->builders_.clear();
+ impl_->modules_->notifyDataFinish();
+}
+
} // namespace gmx
class AbstractAnalysisData;
class AnalysisDataFrameHeader;
class AnalysisDataFrameRef;
+class AnalysisDataModuleManager;
class AnalysisDataParallelOptions;
class AnalysisDataStorage;
namespace internal
{
+class AnalysisDataStorageImpl;
class AnalysisDataStorageFrameData;
} // namespace internal
*
* 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
* 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();
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;
* 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.
*/
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.
*
/*! \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.
*
* 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
* \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
{
}
+int
+AnalysisDataFrameAverageModule::frameCount() const
+{
+ return impl_->storage_.frameCount();
+}
+
int
AnalysisDataFrameAverageModule::flags() const
{
{
setColumnCount(0, data->dataSetCount());
impl_->sampleCount_.resize(data->dataSetCount());
- notifyDataStart();
- impl_->storage_.startDataStorage(this);
+ impl_->storage_.startDataStorage(this, &moduleManager());
}
void
void
AnalysisDataFrameAverageModule::dataFinished()
{
- notifyDataFinish();
+ impl_->storage_.finishDataStorage();
}
AnalysisDataFrameRef
AnalysisDataFrameAverageModule();
virtual ~AnalysisDataFrameAverageModule();
+ virtual int frameCount() const;
+
virtual int flags() const;
virtual void dataStarted(AbstractAnalysisData *data);
#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"
_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;
}
_impl->currValues_.push_back(AnalysisDataValue(dist2));
}
- notifyPointsAdd(AnalysisDataPointSetRef(header, _impl->currValues_));
+ moduleManager().notifyPointsAdd(AnalysisDataPointSetRef(header, _impl->currValues_));
}
- notifyFrameFinish(header);
+ moduleManager().notifyFrameFinish(header);
}
{
if (_impl->nstored >= 2)
{
- notifyDataFinish();
+ moduleManager().notifyDataFinish();
}
}
}
+int
+AnalysisDataSimpleHistogramModule::frameCount() const
+{
+ return impl_->storage_.frameCount();
+}
+
+
int
AnalysisDataSimpleHistogramModule::flags() const
{
{
setColumnCount(i, settings().binCount());
}
- notifyDataStart();
- impl_->storage_.startDataStorage(this);
+ impl_->storage_.startDataStorage(this, &moduleManager());
}
void
AnalysisDataSimpleHistogramModule::dataFinished()
{
- notifyDataFinish();
+ impl_->storage_.finishDataStorage();
}
}
+int
+AnalysisDataWeightedHistogramModule::frameCount() const
+{
+ return impl_->storage_.frameCount();
+}
+
+
int
AnalysisDataWeightedHistogramModule::flags() const
{
{
setColumnCount(i, settings().binCount());
}
- notifyDataStart();
- impl_->storage_.startDataStorage(this);
+ impl_->storage_.startDataStorage(this, &moduleManager());
}
void
AnalysisDataWeightedHistogramModule::dataFinished()
{
- notifyDataFinish();
+ impl_->storage_.finishDataStorage();
}
//! Returns bin properties for the histogram.
const AnalysisHistogramSettings &settings() const;
+ virtual int frameCount() const;
+
virtual int flags() const;
virtual void dataStarted(AbstractAnalysisData *data);
//! \copydoc AnalysisDataSimpleHistogramModule::settings()
const AnalysisHistogramSettings &settings() const;
+ virtual int frameCount() const;
+
virtual int flags() const;
virtual void dataStarted(AbstractAnalysisData *data);