Notice: If you are member of any public project or group, please make sure that your GitLab username is not the same as the LRZ identifier/Kennung (see https://gitlab.lrz.de/profile/account). Please change your username if necessary. For more information see the section "Public projects / Öffentliche Projekte" at https://doku.lrz.de/display/PUBLIC/GitLab . Thank you!

Commit cd8768b5 authored by Jakob Weiss's avatar Jakob Weiss

Draft for writable data with locks

Access to data entries is now managed through two facilities: DataHandles manage metadata and reference counting to handle memory deallocation properly. ScopedTypedData has been extended to manage threadsafe read/write access to the underlying data.
parent d97de6a8
......@@ -27,6 +27,7 @@
#include <memory>
#include <tbb/atomic.h>
#include <tbb/queuing_rw_mutex.h>
#include "cgt/bounds.h"
#include "core/coreapi.h"
......@@ -60,8 +61,14 @@ namespace campvis {
*/
class CAMPVIS_CORE_API AbstractData {
friend class DataHandle;
template<class T, bool isWritable> friend class ScopedTypedData;
public:
// using MutexType = std::shared_timed_mutex;
// using LockType = std::shared_lock < MutexType>;
typedef tbb::queuing_rw_mutex MutexType;
typedef MutexType::scoped_lock LockType;
/**
* Constructor, simply calles ReferenceCounted ctor.
*/
......@@ -102,6 +109,10 @@ 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;
/// This mutex controls the access to the data instance. It should exclusively be managed by ScopedData to ensure
/// consistent locking and releasing of the instance.
mutable MutexType _mutex;
};
}
......
......@@ -62,11 +62,6 @@ namespace campvis {
}
const AbstractData* DataHandle::getData() const {
return _ptr.get();
}
clock_t DataHandle::getTimestamp() const {
return _timestamp;
}
......
......@@ -32,6 +32,7 @@
namespace campvis {
class AbstractData;
template<class T, bool isWritable> class ScopedTypedData;
/**
* A DataHandle is responsible to manage the lifetime of an AbstractData instance.
......@@ -49,6 +50,8 @@ namespace campvis {
* \note Reference counting implementation inspired from Scott Meyers: More Effective C++, Item 29
*/
class CAMPVIS_CORE_API DataHandle {
template<class T, bool isWritable> friend class ScopedTypedData;
public:
/**
* Creates a new DataHandle for the given data.
......@@ -82,7 +85,19 @@ namespace campvis {
* Grants const access to the managed AbstractData instance.
* \return _data;
*/
const AbstractData* getData() const;
template <class T = AbstractData>
ScopedTypedData<T, false> getData() const {
return ScopedTypedData<T, false>(*this, true);
}
/**
* Grants const access to the managed AbstractData instance.
* \return _data;
*/
template <class T = AbstractData>
ScopedTypedData<T, true> getWritableData() {
return ScopedTypedData<T, true>(*this, true);
};
/**
* Gets the timestamp when this data has been created.
......@@ -90,6 +105,21 @@ namespace campvis {
*/
clock_t getTimestamp() const;
/**
* Checks if the handle has a valid pointer.
*/
bool empty() const {
return _ptr.get() == nullptr;
}
/**
* Explicit cast to bool, returns true if pointer is valid. Allows to check DataHandles
* conveniently using an if(dh) { [DataHandle is valid] } statement.
* \return !empty()
*/
operator bool() const {
return !empty();
}
private:
std::shared_ptr<AbstractData> _ptr; ///< managed data
......
......@@ -25,6 +25,8 @@
#ifndef SCOPEDTYPEDDATA_H__
#define SCOPEDTYPEDDATA_H__
#include "core/datastructures/abstractdata.h"
#include "cgt/logmanager.h"
#include "core/datastructures/datacontainer.h"
......@@ -40,8 +42,9 @@ namespace campvis {
*
* \tparam T Base class of the DataHandle data to test for
*/
template<typename T>
struct ScopedTypedData {
template<typename T, bool isWritable = false>
class ScopedTypedData : public AbstractData::LockType {
public:
/**
* 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,24 +52,69 @@ 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)
: AbstractData::LockType()
, dh(dc.getData(name))
, data(nullptr)
{
if (dh.getData() != 0) {
data = dynamic_cast<const T*>(dh.getData());
if (data == 0) {
if (dh) {
data = dynamic_cast<const T*>(dh._ptr.get());
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() << ").");
LDEBUGC("CAMPVis.core.ScopedTypedData (Readonly)", "Found DataHandle with id '" << name << "', but it is of wrong type (" << typeid(dh._ptr.get()).name() << " instead of " << typeid(T).name() << ").");
dh = DataHandle(0);
}
else { // we have a valid handle, so we need to lock it
// lock_shared(dh->_mutex);
acquire(dh._ptr->_mutex, isWritable);
}
}
else {
if (! silent)
LDEBUGC("CAMPVis.core.ScopedTypedData", "Could not find a DataHandle with id '" << name << "' in DataContainer '" << dc.getName() << "'.");
if (!silent)
LDEBUGC("CAMPVis.core.ScopedTypedData (Readonly)", "Could not find a DataHandle with id '" << name << "' in DataContainer '" << dc.getName() << "'.");
}
};
ScopedTypedData(const DataHandle& handle, bool silent = false)
: AbstractData::LockType()
, dh(handle)
, data(nullptr)
{
data = dynamic_cast<const T*>(dh._ptr.get());
if (data == nullptr) {
if (!silent)
LDEBUGC("CAMPVis.core.ScopedTypedData (Readonly)", "DataHandle is of wrong type (" << typeid(dh._ptr.get()).name() << " instead of " << typeid(T).name() << ").");
dh = DataHandle(nullptr);
}
else { // we have a valid handle, so we need to lock it
acquire(dh._ptr->_mutex, isWritable);
}
};
/**
* Move-Constructor that moves the lock over
* \note: Since tbb locks do not support move-construction, this implementation is not very efficient
*/
ScopedTypedData(ScopedTypedData && other)
: AbstractData::LockType()
, dh(other.dh)
, data(other.data)
{
if (data != nullptr) { // we have a valid handle, so we need to lock it
acquire(dh._ptr->_mutex, isWritable);
}
other.dh = DataHandle(nullptr);
other.data = nullptr;
other.release();
}
~ScopedTypedData() {
if(dh)
release(); // we release manually to make sure to release before the datahandle is destroyed - otherwise the mutex is destroyed before it is released
}
/**
* Implicit conversion operator to const T*.
* \return The data in the DataHandle, may be 0 when no DataHandle was found, or the data is of the wrong type.
......@@ -101,6 +149,114 @@ namespace campvis {
const T* data; ///< strongly-typed pointer to data, may be 0
};
template<typename T>
class ScopedTypedData<T, true> : public AbstractData::LockType {
public:
/**
* 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
* \param name Key of the DataHandle to search for
* \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)
: AbstractData::LockType()
, dh(dc.getData(name))
, data(nullptr)
{
if (dh) {
data = dynamic_cast<T*>(dh._ptr.get());
if (data == nullptr) {
if (!silent)
LDEBUGC("CAMPVis.core.ScopedTypedData (Writable)", "Found DataHandle with id '" << name << "', but it is of wrong type (" << typeid(dh._ptr.get()).name() << " instead of " << typeid(T).name() << ").");
dh = DataHandle(nullptr);
}
else { // we have a valid handle, so we need to lock it
// lock(data->_mutex);
acquire(dh._ptr->_mutex, true);
}
}
else {
if (!silent)
LDEBUGC("CAMPVis.core.ScopedTypedData (Writable)", "Could not find a DataHandle with id '" << name << "' in DataContainer '" << dc.getName() << "'.");
}
};
ScopedTypedData(const DataHandle& handle, bool silent = false)
: AbstractData::LockType()
, dh(handle)
, data(nullptr)
{
data = dynamic_cast<T*>(dh._ptr.get());
if (data == nullptr) {
if (!silent)
LDEBUGC("CAMPVis.core.ScopedTypedData (Writable)", "DataHandle is of wrong type (" << typeid(dh._ptr.get()).name() << " instead of " << typeid(T).name() << ").");
dh = DataHandle(nullptr);
}
else { // we have a valid handle, so we need to lock it
// lock(data->_mutex);
acquire(dh._ptr->_mutex, true);
}
};
/**
*/
ScopedTypedData(ScopedTypedData && other)
: AbstractData::LockType()
, dh(other.dh)
, data(other.data)
{
if (data != nullptr) { // we have a valid handle, so we need to lock it
other.release(); // we need to release the other lock first because we cannot move it directly
acquire(dh._ptr->_mutex, true);
}
other.dh = DataHandle(nullptr);
other.data = nullptr;
}
~ScopedTypedData() {
if(dh)
release(); // we release manually to make sure to release before the datahandle is destroyed - otherwise the mutex is destroyed before it is released
}
/**
* Implicit conversion operator to const T*.
* \return The data in the DataHandle, may be 0 when no DataHandle was found, or the data is of the wrong type.
*/
operator T*() {
return data;
}
/**
* Implicit arrow operator to T*.
* \return The data in the DataHandle, may be 0 when no DataHandle was found, or the data is of the wrong type.
*/
T* operator->() {
return data;
}
/**
* Gets the DataHandle.
* \return dh
*/
DataHandle getDataHandle() {
return dh;
}
private:
/// Not copy-constructable
ScopedTypedData(const ScopedTypedData& rhs);
/// Not assignable
ScopedTypedData& operator=(const ScopedTypedData& rhs);
DataHandle dh; ///< DataHandle
T* data; ///< strongly-typed pointer to data, may be 0
};
}
#endif // SCOPEDTYPEDDATA_H__
\ No newline at end of file
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