04.06., 9:00 - 12:00: GitLab will be migrated to a new server environment and upgraded to Enterprise Edition Ultimate. The estimated downtime will be 2-3 hours. Please see https://doku.lrz.de/display/PUBLIC/GitLab+Ultimate+Migration for more details about changes related to the migration.

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