[WIP] Started drafting writable data semantics.

parent 113239ac
......@@ -53,13 +53,12 @@ namespace campvis {
// ================================================================================================
class DataHandle;
/**
* Abstract base class for data handled by a DataHandle and stored in a DataContainer.
*/
class CAMPVIS_CORE_API AbstractData {
friend class DataHandle;
template<bool isWriteLock>
friend class AccessLockedAbstractData;
public:
/**
......@@ -102,6 +101,9 @@ namespace campvis {
/// Should be only accessed by DataHandle (therefore the friendship) in order to avoid
/// multiple owning groups for the same object.
std::weak_ptr<AbstractData> _weakPtr;
/// Reader-writer mutex for accessing the data.
mutable tbb::queuing_rw_mutex _mutex;
};
}
......
......@@ -41,10 +41,10 @@ namespace campvis {
_handles.clear();
}
DataHandle DataContainer::addData(const std::string& name, AbstractData* data) {
void DataContainer::addData(const std::string& name, AbstractData* data) {
if (name.empty()) {
LERROR("Tried to add data with empty name to DataContainer.");
return DataHandle(0);
return;
}
cgtAssert(data != 0, "The Data must not be 0.");
......@@ -52,7 +52,6 @@ namespace campvis {
DataHandle dh(data);
addDataHandle(name, dh);
return dh;
}
void DataContainer::addDataHandle(const std::string& name, DataHandle dh) {
......@@ -75,16 +74,6 @@ namespace campvis {
return _handles.find(a, name);
}
DataHandle DataContainer::getData(const std::string& name) const {
tbb::concurrent_hash_map<std::string, DataHandle>::const_accessor a;
if (_handles.find(a, name)) {
return a->second;
}
else {
return DataHandle(0);
}
}
void DataContainer::removeData(const std::string& name) {
_handles.erase(name);
}
......@@ -94,7 +83,7 @@ namespace campvis {
toReturn.reserve(_handles.size());
tbb::spin_mutex::scoped_lock lock(_localMutex);
for (tbb::concurrent_hash_map<std::string, DataHandle>::const_iterator it = _handles.begin(); it != _handles.end(); ++it) {
for (auto it = _handles.begin(); it != _handles.end(); ++it) {
toReturn.push_back(std::make_pair(it->first, it->second));
}
......
......@@ -27,6 +27,7 @@
#include "sigslot/sigslot.h"
#include <tbb/concurrent_hash_map.h>
#include <tbb/queuing_rw_mutex.h>
#include <tbb/spin_mutex.h>
#include "core/coreapi.h"
......@@ -39,7 +40,70 @@
namespace campvis {
class AbstractData;
template<bool writeAccess = false>
class ScopedData : public tbb::queuing_rw_mutex::scoped_lock {
public:
ScopedData()
: tbb::queuing_rw_mutex::scoped_lock()
, _data(nullptr)
{}
ScopedData(std::shared_ptr<AbstractData> data)
: tbb::queuing_rw_mutex::scoped_lock(data->_mutex, writeAccess)
, _data(data)
{}
ScopedData(const ScopedData<writeAccess>& other)
: tbb::queuing_rw_mutex::scoped_lock(other._data->_mutex, writeAccess)
, _data(other._data)
{}
ScopedData<writeAccess>& operator=(ScopedData<writeAccess> rhs) {
// tbb::queuing_rw_mutex::scoped_lock is not_assignable. Let's pray that this release-acquire move is working as intended...
this->release();
std::swap(this->_data, rhs._data);
this->acquire(_data->_mutex, writeAccess);
return *this;
}
const AbstractData* getData() const {
return _data.get();
}
AbstractData* getWritableData() {
static_assert(writeAccess, "Can not access non-const AbstractData from a read-only AccessLock!");
return _data.get();
}
/**
* Implicit conversion operator to const AbstractData*.
*/
operator const AbstractData*() const {
return _data.get();
}
/**
* Implicit arrow operator to const AbstractData*.
*/
const AbstractData* operator->() const {
return _data.get();
}
DataHandle getDataHandle() const {
return _data;
}
WritableDataHandle getWritableDataHandle() const {
static_assert(writeAccess, "Can not access non-const DataHandle from a read-only AccessLock!");
return _data;
}
private:
WritableDataHandle _data;
};
/**
* A DataContainer manages instances of AbstractData and offers access to them via string identifiers (names/keys).
* Therefore, it stores them in DataHandles which take ownership of the AbstractData instance. Hence,
......@@ -71,9 +135,8 @@ namespace campvis {
*
* \param name Key for accessing the DataHandle within this DataContainer.
* \param data The data to wrap in a DataHandle and add to this DataContainer, must not be 0.
* \return A DataHandle containing \a data.
*/
DataHandle addData(const std::string& name, AbstractData* data);
void addData(const std::string& name, AbstractData* data);
/**
* Adds the given DataHandle \a data, accessible by the key \name, to this DataContainer.
......@@ -99,7 +162,16 @@ namespace campvis {
* \param name Key of the DataHandle to search for
* \return The stored DataHandle with the given name, an empty DataHandle if no such DataHandle exists.
*/
DataHandle getData(const std::string& name) const;
template<bool writeAccess = false>
ScopedData<writeAccess> getData(const std::string& name) const {
tbb::concurrent_hash_map<std::string, DataHandle>::const_accessor a;
if (_handles.find(a, name)) {
return ScopedData<writeAccess>(a->second);
}
else {
return ScopedData<writeAccess>();
}
}
/**
* Removes the DataHandle with the given name from this container.
......
......@@ -62,12 +62,10 @@ namespace campvis {
}
const AbstractData* DataHandle::getData() const {
return _ptr.get();
}
clock_t DataHandle::getTimestamp() const {
auto a = getData();
auto b = getData<true>();
return _timestamp;
}
......
......@@ -33,6 +33,12 @@
namespace campvis {
class AbstractData;
typedef std::shared_ptr<const AbstractData> DataHandle;
typedef std::shared_ptr<AbstractData> WritableDataHandle;
#if 0
/**
* A DataHandle is responsible to manage the lifetime of an AbstractData instance.
* Therefore, it implements a reference counting technique in cooperation with AbstractData.
......@@ -42,11 +48,10 @@ namespace campvis {
* * Concurrent access to the same AbstractData instance via different DataHandles is safe.
*
* \note For clarity: An AbstractData instance can be referenced by multiple DataHandles. As soon
* as it is afterwards reference by 0 DataHandles, the AbstractData instance will be destroyed.
* as it is referenced by 0 DataHandles afterwards, the AbstractData instance will be destroyed.
* Also remember that a DataHandle takes ownership of the given AbstractData instance. So do
* not delete it once it has been assigned to a DataHandle (respectively DataContainer) or mess
* with its reference counting!
* \note Reference counting implementation inspired from Scott Meyers: More Effective C++, Item 29
*/
class CAMPVIS_CORE_API DataHandle {
public:
......@@ -82,7 +87,10 @@ namespace campvis {
* Grants const access to the managed AbstractData instance.
* \return _data;
*/
const AbstractData* getData() const;
template<bool writeAccess = false>
AccessLockedAbstractData<writeAccess> getData() const {
return _ptr ? AccessLockedAbstractData<writeAccess>(_ptr) : AccessLockedAbstractData<writeAccess>();
}
/**
* Gets the timestamp when this data has been created.
......@@ -95,7 +103,7 @@ namespace campvis {
std::shared_ptr<AbstractData> _ptr; ///< managed data
clock_t _timestamp; ///< Timestamp when this data has been created
};
#endif
}
#endif // datahandle_h__
\ No newline at end of file
......@@ -38,10 +38,11 @@ namespace campvis {
* reference counting of a DataHandle. Use this class when you want temporary access to a strongly-typed
* data item in a DataContainer but don't want to to the dynamic_cast yourself.
*
* \tparam T Base class of the DataHandle data to test for
* \tparam T Base class of the DataHandle data to test for
* \tparam writeAccess Flag whether to support write access to the underlying data. Use sparingly since this requires exclusive access to the data.
*/
template<typename T>
struct ScopedTypedData {
template<typename T, bool writeAccess = false>
struct ScopedTypedData : public ScopedData<writeAccess> {
/**
* Creates a new DataHandle to the data item with the key \a name in \a dc, that behaves like a T*.
* \param dc DataContainer to grab data from
......@@ -49,16 +50,16 @@ namespace campvis {
* \param silent Flag whether debug messages when no matching data is found should be silenced (defaults to false).
*/
ScopedTypedData(const DataContainer& dc, const std::string& name, bool silent = false)
: dh(dc.getData(name))
, data(0)
: scopedData(dc.getData(name))
, data(nullptr)
{
if (dh.getData() != 0) {
data = dynamic_cast<const T*>(dh.getData());
if (data == 0) {
if (scopedData.getData() != nullptr) {
data = dynamic_cast<const T*>(scopedData.getData());
if (data == nullptr) {
if (!silent)
LDEBUGC("CAMPVis.core.ScopedTypedData", "Found DataHandle with id '" << name << "', but it is of wrong type (" << typeid(*dh.getData()).name() << " instead of " << typeid(T).name() << ").");
dh = DataHandle(0);
scopedData = ScopedData<writeAccess>();
}
}
else {
......@@ -88,7 +89,7 @@ namespace campvis {
* \return dh
*/
DataHandle getDataHandle() const {
return dh;
return scopedData.getDataHandle();
}
private:
......@@ -97,8 +98,8 @@ namespace campvis {
/// Not assignable
ScopedTypedData& operator=(const ScopedTypedData& rhs);
DataHandle dh; ///< DataHandle
const T* data; ///< strongly-typed pointer to data, may be 0
ScopedData<writeAccess> scopedData; ///< Access lock structure around the data.
const T* data; ///< strongly-typed pointer to data, may be 0
};
}
......
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