Commit c7b40fe7 authored by Sebastian Eichelbaum's avatar Sebastian Eichelbaum

[CHANGE] - modules can now forcibly trigger updates on their output...

[CHANGE] - modules can now forcibly trigger updates on their output connectors. In compliance with this change, the input connector interface has been extended. See template module for usage details. Also done this for bounding box module and raycast-isosurface module.
parent c359cdaf
......@@ -22,7 +22,6 @@
//
//---------------------------------------------------------------------------
#include <iostream>
#include <string>
#include "WModule.h"
......@@ -32,7 +31,8 @@
#include "WModuleInputConnector.h"
WModuleInputConnector::WModuleInputConnector( boost::shared_ptr< WModule > module, std::string name, std::string description ):
WModuleConnector( module, name, description )
WModuleConnector( module, name, description ),
m_updated( false )
{
// initialize members
......@@ -95,6 +95,8 @@ boost::signals2::connection WModuleInputConnector::subscribeSignal( MODULE_CONNE
void WModuleInputConnector::notifyDataChange( boost::shared_ptr<WModuleConnector> /*input*/,
boost::shared_ptr<WModuleConnector> output )
{
setUpdated();
// since the output connector is not able to fill the parameter "input" we need to forward this message and fill it with the
// proper information
signal_DataChanged( shared_from_this(), output );
......@@ -126,3 +128,24 @@ bool WModuleInputConnector::isOutputConnector() const
{
return false;
}
bool WModuleInputConnector::updated()
{
boost::lock_guard<boost::shared_mutex> lock( m_updatedLock );
return m_updated;
}
void WModuleInputConnector::setUpdated()
{
boost::lock_guard<boost::shared_mutex> lock( m_updatedLock );
m_updated = true;
}
bool WModuleInputConnector::handledUpdate()
{
boost::lock_guard<boost::shared_mutex> lock( m_updatedLock );
bool old = m_updated;
m_updated = false;
return old;
}
......@@ -27,6 +27,8 @@
#include <string>
#include <boost/thread/locks.hpp>
#include "WModule.h"
#include "WModuleConnector.h"
#include "WModuleConnectorSignals.h"
......@@ -94,6 +96,20 @@ public:
*/
virtual bool isOutputConnector() const;
/**
* Denotes whether the connected output was updated. This does NOT denote an actual change in the current data!
*
* \return true if there has been an update.
*/
virtual bool updated();
/**
* Resets the updated-flag. Call this from your module to reset the value of updated().
*
* \return the update flag before reset. Useful to get the flag and reset it in one call.
*/
virtual bool handledUpdate();
protected:
/**
......@@ -127,6 +143,11 @@ protected:
*/
virtual void notifyConnectionEstablished( boost::shared_ptr<WModuleConnector> here, boost::shared_ptr<WModuleConnector> there );
/**
* Sets the update flag (use updated() to query it)to true. This is normally called by the notifyDataChange callback.
*/
virtual void setUpdated();
private:
/**
......@@ -145,6 +166,16 @@ private:
* Connection for Data Changed signal of the connected output connector.
*/
boost::signals2::connection m_DataChangedConnection;
/**
* This lock protects the m_updated flag.
*/
boost::shared_mutex m_updatedLock;
/**
* A flag denoting that an update was received. It does not denote a real change in the value!
*/
bool m_updated;
};
#endif // WMODULEINPUTCONNECTOR_H
......
......@@ -79,6 +79,8 @@ public:
// get a lock
boost::shared_lock<boost::shared_mutex> lock = boost::shared_lock<boost::shared_mutex>( m_connectionListLock );
handledUpdate();
// is there something in the list?
if ( m_connected.begin() == m_connected.end() )
{
......
......@@ -77,6 +77,15 @@ public:
{
m_data = data;
// broadcast this event
triggerUpdate();
};
/**
* This method simply propagates an update but does not actually change the data.
*/
virtual void triggerUpdate()
{
// broadcast this event
propagateDataChange();
};
......
......@@ -225,6 +225,7 @@ void WMData::propertyChanged( boost::shared_ptr< WPropertyBase > property )
wmath::WPosition pos( m_translationX->get(), m_translationY->get(), m_translationZ->get() );
grid->translate( pos );
WDataHandler::getDefaultSubject()->getChangeCondition()->notify();
m_output->triggerUpdate();
}
else if ( property == m_stretchX || property == m_stretchY || property == m_stretchZ )
{
......@@ -232,6 +233,7 @@ void WMData::propertyChanged( boost::shared_ptr< WPropertyBase > property )
wmath::WPosition str( m_stretchX->get(), m_stretchY->get(), m_stretchZ->get() );
grid->stretch( str );
WDataHandler::getDefaultSubject()->getChangeCondition()->notify();
m_output->triggerUpdate();
}
else if ( property == m_rotationX || property == m_rotationY || property == m_rotationZ )
{
......@@ -244,12 +246,14 @@ void WMData::propertyChanged( boost::shared_ptr< WPropertyBase > property )
wmath::WPosition rot( rotx, roty, rotz );
grid->rotate( rot );
WDataHandler::getDefaultSubject()->getChangeCondition()->notify();
m_output->triggerUpdate();
}
else if ( property == m_matrixSelection )
{
boost::shared_ptr< WGridRegular3D > grid = m_dataSet->getTexture()->getGrid();
grid->setActiveMatrix( m_matrixSelection->get( true ).getItemIndexOfSelected( 0 ) );
WDataHandler::getDefaultSubject()->getChangeCondition()->notify();
m_output->triggerUpdate();
}
}
else
......
......@@ -94,8 +94,8 @@ void WMBoundingBox::moduleMain()
while ( !m_shutdownFlag() )
{
// acquire data from the input connector
m_dataSet = m_input->getData();
if ( !m_dataSet.get() )
boost::shared_ptr< WDataSetSingle > dataSet = m_input->getData();
if ( !dataSet )
{
// OK, the output has not yet sent data
// NOTE: see comment at the end of this while loop for m_moduleState
......@@ -104,10 +104,12 @@ void WMBoundingBox::moduleMain()
continue;
}
boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( m_dataSet->getGrid() );
boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( dataSet->getGrid() );
WAssert( grid, "Seems that grid is of wrong type." );
WGraphicsEngine::getGraphicsEngine()->getScene()->remove( m_bBoxNode );
std::pair< wmath::WPosition, wmath::WPosition > bb = grid->getBoundingBox();
m_bBoxNode = osg::ref_ptr< WGEGroupNode >( new WGEGroupNode );
......
......@@ -114,6 +114,5 @@ private:
osg::ref_ptr< WGEGroupNode > m_bBoxNode; //!< OSG root node for this module
boost::shared_ptr< WModuleInputData< WDataSetSingle > > m_input; //!< Input connector required by this module.
boost::shared_ptr< WDataSetSingle > m_dataSet; //!< Pointer providing access to the treated data set in the whole module.
};
#endif // WMBOUNDINGBOX_H
......@@ -22,6 +22,8 @@
//
//---------------------------------------------------------------------------
#include <string>
#include "../../kernel/WKernel.h"
#include "../../common/WPropertyHelper.h"
......@@ -63,13 +65,27 @@ const std::string WMHistogramEqualization::getDescription() const
void WMHistogramEqualization::connectors()
{
// call WModules initialization
// the dataset to process. Only accept scalar data.
m_input = boost::shared_ptr< WModuleInputData < WDataSetScalar > >(
new WModuleInputData< WDataSetScalar >( shared_from_this(),
"in", "The dataset whose histogram gets equalized." )
);
addConnector( m_input );
// the output containing the equalized data.
m_output = boost::shared_ptr< WModuleOutputData < WDataSetScalar > >(
new WModuleOutputData< WDataSetScalar >( shared_from_this(),
"out", "The dataset which has a linear cumulative histogram." )
);
addConnector( m_output );
// call WModule's initialization
WModule::connectors();
}
void WMHistogramEqualization::properties()
{
// call WModules initialization
// call WModule's initialization
WModule::properties();
}
......
......@@ -25,6 +25,8 @@
#ifndef WMHISTOGRAMEQUALIZATION_H
#define WMHISTOGRAMEQUALIZATION_H
#include <string>
#include "../../dataHandler/WDataSetScalar.h"
#include "../../kernel/WModule.h"
......
......@@ -152,33 +152,25 @@ void WMIsosurfaceRaytracer::moduleMain()
break;
}
// has the data changed?
boost::shared_ptr< WDataSetScalar > newDataSet = m_input->getData();
bool dataChanged = ( m_dataSet != newDataSet );
bool dataValid = ( newDataSet );
// are there new valid data?
if ( dataChanged && dataValid )
{
// The data is different. Copy it to our internal data variable:
debugLog() << "Received Data.";
m_dataSet = newDataSet;
}
// was there an update?
bool dataUpdated = m_input->updated();
boost::shared_ptr< WDataSetScalar > dataSet = m_input->getData();
bool dataValid = ( dataSet );
// m_isoColor or shading changed
if ( m_isoColor->changed() || m_shadingAlgo->changed() )
{
// a new color requires the proxy geometry to be rebuild as we store it as color in this geometry
dataChanged = true;
dataUpdated = true;
}
// As the data has changed, we need to recreate the texture.
if ( dataChanged && dataValid )
if ( dataUpdated && dataValid )
{
debugLog() << "Data changed. Uploading new data as texture.";
// First, grab the grid
boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( m_dataSet->getGrid() );
boost::shared_ptr< WGridRegular3D > grid = boost::shared_dynamic_cast< WGridRegular3D >( dataSet->getGrid() );
if ( !grid )
{
errorLog() << "The dataset does not provide a regular grid. Ignoring dataset.";
......@@ -197,7 +189,7 @@ void WMIsosurfaceRaytracer::moduleMain()
m_shader->apply( cube );
// bind the texture to the node
osg::ref_ptr< osg::Texture3D > texture3D = m_dataSet->getTexture()->getTexture();
osg::ref_ptr< osg::Texture3D > texture3D = dataSet->getTexture()->getTexture();
osg::StateSet* rootState = cube->getOrCreateStateSet();
rootState->setTextureAttributeAndModes( 0, texture3D, osg::StateAttribute::ON );
......
......@@ -114,11 +114,6 @@ private:
*/
boost::shared_ptr< WModuleInputData< WDataSetScalar > > m_input;
/**
* This is a pointer to the dataset the module is currently working on.
*/
boost::shared_ptr< WDataSetScalar > m_dataSet;
/**
* The Isovalue used in the case m_isoSurface is true.
*/
......
......@@ -404,33 +404,36 @@ void WMTemplate::moduleMain()
// After collection, the calculation work can be done.
// Now, we can check the input, whether it changed the data. Therefore, we try to grab some data and check whether it is different from
// the currently used data. Of course, you do not need to check whether the data really is different, but you should do it as you do not
// want permanent recalculation which is actually not needed.
//
// Note: do not call m_input->getData() twice; one for checking if it is different and on for copying the pointer, since the result of
// getData might be different among both calls.
boost::shared_ptr< WDataSetSingle > newDataSet = m_input->getData();
bool dataChanged = ( m_dataSet != newDataSet );
bool dataValid = ( newDataSet );
// To check validity of multiple inputs at once, you can also use dataChanged and dataValid:
// bool dataChanged = ( m_dataSet != newDataSet ) || ( m_dataSet2 != newDataSet2 ) || ( m_dataSet3 != newDataSet3 );
// bool dataValid = newDataSet && newDataSet2 && newDataSet3;
// This way, you can easily ensure that ALL your inputs are set and the module can do its job
// now, copy the new data to out local member variables
if ( dataChanged && dataValid )
// this condition will become true whenever the new data is different from the current one or our actual data is NULL. This handles all
// cases.
// Now, we can check the input, whether there is an update enqueued for us. But first, we need to understand the meaning of an update on
// an input connector:
// * a module updates its output connector with some new data -> updated
// * a module triggers an update on its output connector without an actual data change -> updated
// * our own input connector got connected to an output connector -> updated
// * our own input connector got DISconnected from an output connector -> NO update
// You now might ask: "Why can modules trigger updates if they did not change the data?". The answer is simple. Some modules change the
// grid without actually changing the data for example. They translate the grid in space. This results in an update although the actual
// data stayed the same.
// To query whether an input was updated, simply ask the input:
bool dataUpdated = m_input->updated();
// Remember the above criteria. We now need to check if the data is valid. After a connect-update, it might be NULL.
boost::shared_ptr< WDataSetSingle > dataSet = m_input->getData();
bool dataValid = ( dataSet );
// After calling getData(), the update flag is reset and false again. Please keep in mind, that the module lives in an multi-threaded
// world where the update flag and data can change at any time. DO NEVER use getData directly in several places of your module as the
// data returned may change between two consecutive calls! Always grab it into a local variable and use this variable.
// Another important hint. For grabbing the data, use a local variable wherever possible. If you use a member variable, the data might
// never be freed if nobody uses the data anymore because your module holds the last reference. If you need to use a member variable for
// the received data, subscribe the your input's disconnect signal or overwrite WModule::notifyConnectionClosed and reset your variable
// there to ensure its proper deletion.
// do something with the data
if ( dataUpdated && dataValid )
{
// The data is different. Copy it to our internal data variable:
// The data is valid and we received an update. The data is not NULL but may be the same as in previous loops.
debugLog() << "Received Data.";
m_dataSet = newDataSet;
// For multiple inputs:
// m_dataSet2 = newDataSet2;
// m_dataSet3 = newDataSet3;
}
// Here we collect our properties. You, as with input connectors, always check if a property really has changed. You most probably do not
......@@ -447,7 +450,7 @@ void WMTemplate::moduleMain()
// This is a simple example for doing an operation which is not depending on any other property.
debugLog() << "Doing an operation on the file \"" << m_aFile->get( true ).file_string() << "\".";
// NOTE: be careful if you want to use m_dataSet here, as it might be unset. Verify data validity using dataChanged && dataValid.
// NOTE: be careful if you want to use dataSet here, as it might be unset. Verify data validity using dataUpdated && dataValid.
}
// m_aFile got handled above. Now, handle two properties which together are used as parameters for an operation.
......@@ -457,12 +460,12 @@ void WMTemplate::moduleMain()
debugLog() << "Doing an operation basing on m_aString ... ";
debugLog() << "m_aString: " << m_aString->get( true );
// NOTE: be careful if you want to use m_dataSet here, as it might be unset. Verify data validity using dataChanged && dataValid.
// NOTE: be careful if you want to use dataSet here, as it might be unset. Verify data validity using dataUpdated && dataValid.
}
// This example code now shows how to modify your OSG nodes basing on changes in your dataset or properties.
// The if statement also checks for data validity as it uses the data! You should also always do that.
if ( ( m_anInteger->changed() || m_aDouble->changed() || dataChanged ) && dataValid )
if ( ( m_anInteger->changed() || m_aDouble->changed() || dataUpdated ) && dataValid )
{
debugLog() << "Creating new OSG node";
......@@ -478,7 +481,7 @@ void WMTemplate::moduleMain()
debugLog() << "Number of Rows: " << rows;
debugLog() << "Radii: " << radii;
debugLog() << "Current dataset: " << m_dataSet->getFileName() << " with name: " << m_dataSet->getName();
debugLog() << "Current dataset: " << dataSet->getFileName() << " with name: " << dataSet->getName();
// This block will be executed whenever we have a new dataset or the m_anInteger property has changed. This example codes produces
// some shapes and replaces the existing root node by a new (updated) one. Therefore, a new root node is needed:
......@@ -517,13 +520,13 @@ void WMTemplate::moduleMain()
// Now we updated the visualization after the dataset has changed. Your module might also calculate some other datasets basing on the
// input data.
// To ensure that all datasets are valid, check dataChanged and dataValid. If both are true, you can safely use the data.
if ( dataChanged && dataValid )
// To ensure that all datasets are valid, check dataUpdated and dataValid. If both are true, you can safely use the data.
if ( dataUpdated && dataValid )
{
debugLog() << "Data changed. Recalculating output.";
// Calculate your new data here. This example just forwards the input to the output ;-).
boost::shared_ptr< WDataSetSingle > newData = m_dataSet;
boost::shared_ptr< WDataSetSingle > newData = dataSet;
// Doing a lot of work without notifying the user visually is not a good idea. So how is it possible to report progress? Therefore,
// the WModule class provides a member m_progress which is of type WPropgressCombiner. You can create own progress objects and count
......
......@@ -127,11 +127,6 @@ private:
*/
boost::shared_ptr< WModuleOutputData< WDataSetSingle > > m_output;
/**
* This is a pointer to the dataset the module is currently working on.
*/
boost::shared_ptr< WDataSetSingle > m_dataSet;
/**
* A condition used to notify about changes in several properties.
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment