Skip to content

Commit

Permalink
libcore: Typed template for PointerSets; finished implementation
Browse files Browse the repository at this point in the history
PointerSetT<> can be used to make pointer sets of any type.

PointerSet now supports iteration observers that allow the allocation
to be changed without screwing up the iterators.

Several bugs were fixed.
  • Loading branch information
skyjake committed Feb 12, 2017
1 parent f81a6fa commit c0883d1
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 30 deletions.
70 changes: 63 additions & 7 deletions doomsday/sdk/libcore/include/de/data/pointerset.h
Expand Up @@ -41,7 +41,16 @@ class DENG2_PUBLIC PointerSet
typedef duint16 Flag;

static Flag const AllowInsertionDuringIteration;
static Flag const BeingIterated;

/// Addition was not possible because the set is being iterated.
DENG2_ERROR(AdditionForbiddenError);

class DENG2_PUBLIC IIterationObserver
{
public:
virtual ~IIterationObserver() {}
virtual void pointerSetIteratorsWereInvalidated(Pointer const *oldBase, Pointer const *newBase) = 0;
};

public:
PointerSet();
Expand All @@ -55,32 +64,79 @@ class DENG2_PUBLIC PointerSet
bool contains(Pointer ptr) const;
void clear();

PointerSet &operator = (PointerSet const &other);
PointerSet &operator = (PointerSet &&moved);

inline void setFlags(Flag flags, FlagOpArg op = SetFlags) {
applyFlagOperation(_flags, flags, op);
}

inline Flag flags() const { return _flags; }
inline int size() const { return _range.size(); }
inline Rangeui16 usedRange() const { return _range; }
inline int allocatedSize() const { return _size; }
inline const_iterator begin() const { return _pointers + _range.start; }
inline const_iterator end() const { return _pointers + _range.end; }

inline void setFlags(Flag flags, FlagOpArg op = SetFlags) {
applyFlagOperation(_flags, flags, op);
}
void setBeingIterated(bool yes) const;
bool isBeingIterated() const;

void setIterationObserver(IIterationObserver *observer) const;
inline IIterationObserver *iterationObserver() const { return _iterationObserver; }

protected:
Rangeui16 locate(Pointer ptr) const;

inline Pointer at(duint16 pos) const { return _pointers[pos]; }

private:
Pointer *_pointers;
duint16 _flags;
mutable IIterationObserver *_iterationObserver;
mutable duint16 _flags;
duint16 _size;
Rangeui16 _range;
};

} // namespace de
/**
* Utility template for storing a particular type of pointers in a PointerSet.
*/
template <typename Type>
class PointerSetT : public PointerSet
{
public:
typedef PointerSet::Pointer BasePointer;
typedef Type * Pointer;
typedef Pointer const * const_iterator;

public:
PointerSetT() {}
PointerSetT(PointerSetT const &other) : PointerSet(other) {}
PointerSetT(PointerSetT &&moved) : PointerSet(moved) {}

inline PointerSetT &operator = (PointerSetT const &other) {
PointerSet::operator = (other);
return *this;
}
inline PointerSetT &operator = (PointerSetT &&moved) {
PointerSet::operator = (moved);
return *this;
}
inline void insert(Pointer ptr) {
PointerSet::insert(reinterpret_cast<BasePointer>(ptr));
}
inline void remove(Pointer ptr) {
PointerSet::remove(reinterpret_cast<BasePointer>(ptr));
}
inline bool contains(Pointer ptr) const {
return PointerSet::contains(reinterpret_cast<BasePointer>(ptr));
}
inline const_iterator begin() const {
return reinterpret_cast<const_iterator>(PointerSet::begin());
}
inline const_iterator end() const {
return reinterpret_cast<const_iterator>(PointerSet::end());
}
};

} // namespace de

#endif // LIBDENG2_POINTERSET_H
116 changes: 98 additions & 18 deletions doomsday/sdk/libcore/src/data/pointerset.cpp
Expand Up @@ -21,20 +21,22 @@

namespace de {

static duint16 const POINTERSET_MIN_ALLOC = 2;
static duint16 const POINTERSET_MAX_SIZE = 0xffff;
static duint16 const POINTERSET_MIN_ALLOC = 2;
static duint16 const POINTERSET_MAX_SIZE = 0xffff;
static PointerSet::Flag const POINTERSET_ITERATION_MASK = 0x00ff;

PointerSet::Flag const PointerSet::AllowInsertionDuringIteration = 0x1;
PointerSet::Flag const PointerSet::BeingIterated = 0x2;
PointerSet::Flag const PointerSet::AllowInsertionDuringIteration = 0x8000;

PointerSet::PointerSet()
: _pointers(nullptr)
, _flags (0)
, _size (0)
, _iterationObserver(nullptr)
, _flags(0)
, _size (0)
{}

PointerSet::PointerSet(PointerSet const &other)
: _flags(other._flags)
: _iterationObserver(other._iterationObserver)
, _flags(other._flags)
, _size (other._size)
, _range(other._range)
{
Expand All @@ -45,15 +47,21 @@ PointerSet::PointerSet(PointerSet const &other)

PointerSet::PointerSet(PointerSet &&moved)
: _pointers(moved._pointers)
, _flags (moved._flags)
, _size (moved._size)
, _range (moved._range)
, _iterationObserver(moved._iterationObserver)
, _flags(moved._flags)
, _size (moved._size)
, _range(moved._range)
{
moved._pointers = nullptr; // taken
}

PointerSet::~PointerSet()
{
// PointerSet must not be deleted while someone is iterating it. If this happens,
// you need to use Garbage instead of deleting the object immediately (e.g.,
// deleting in response to a audience notification).
DENG2_ASSERT(!isBeingIterated());

free(_pointers);
}

Expand All @@ -79,20 +87,40 @@ void PointerSet::insert(Pointer ptr)
auto const loc = locate(ptr);
if (!loc.isEmpty()) return; // Already got it.

if (_flags & BeingIterated)
if (isBeingIterated())
{
DENG2_ASSERT(_flags & AllowInsertionDuringIteration);

if (!(_flags & AllowInsertionDuringIteration))
{
// This would likely cause the iteration to skip or repeat an item,
// or even segfault if a reallocation occurs. Normally we will never
// get here (user must ensure that the AllowInsertionDuringIteration
// flag is set if needed).
return;
}

// User must be aware that the allocation may change.
DENG2_ASSERT(_iterationObserver != nullptr);
}

// Do we need to expand?
// Expand the array when the used range covers the entire array.
if (_range.size() == _size)
{
DENG2_ASSERT(_size < POINTERSET_MAX_SIZE);

Pointer *oldBase = _pointers;
duint const oldSize = _size;

_size = (_size < 0x8000? (_size * 2) : POINTERSET_MAX_SIZE);
_pointers = reinterpret_cast<Pointer *>(realloc(_pointers, sizeof(Pointer) * _size));
std::memset(_pointers + oldSize, 0, sizeof(Pointer) * (_size - oldSize));

// If someone is interested, let them know about the relocation.
if (_iterationObserver && _pointers != oldBase)
{
_iterationObserver->pointerSetIteratorsWereInvalidated(oldBase, _pointers);
}
}

// Addition to the ends with room to spare?
Expand All @@ -105,12 +133,16 @@ void PointerSet::insert(Pointer ptr)
{
_pointers[_range.end++] = ptr;
}
else // Need to move first to make room.
else
{
// We need to move existing items first to make room for the insertion.

// Figure out the smallest portion of the range that needs to move.
duint16 const middle = (_range.start + _range.end + 1)/2;
if ((pos > middle && _range.end < _size) || // Less stuff to move toward the end.
_range.start == 0)
{
// Move the second half of the range forward, extending it by one.
DENG2_ASSERT(_range.end < _size);
std::memmove(_pointers + pos + 1,
_pointers + pos,
Expand All @@ -120,11 +152,14 @@ void PointerSet::insert(Pointer ptr)
}
else
{
// Have to move the first half of the range backward.
DENG2_ASSERT(_range.start > 0);
std::memmove(_pointers + _range.start - 1,
_pointers + _range.start,
sizeof(Pointer) * (pos - _range.start + 1));
_range.start--;
sizeof(Pointer) * (pos < _range.end? (pos - _range.start + 1) :
(_range.size())));
_pointers[pos - 1] = ptr;
_range.start--;
}
}
}
Expand All @@ -133,6 +168,7 @@ void PointerSet::insert(Pointer ptr)
void PointerSet::remove(Pointer ptr)
{
auto const loc = locate(ptr);

if (!loc.isEmpty())
{
DENG2_ASSERT(!_range.isEmpty());
Expand All @@ -142,8 +178,7 @@ void PointerSet::remove(Pointer ptr)
{
_pointers[_range.start++] = nullptr;
}
else if (loc.start == _range.end - 1 &&
!(_flags & BeingIterated))
else if (loc.start == _range.end - 1 && !isBeingIterated())
{
_pointers[--_range.end] = nullptr;
}
Expand All @@ -170,10 +205,26 @@ void PointerSet::clear()
if (_pointers)
{
std::memset(_pointers, 0, sizeof(Pointer) * _size);
_range = Rangeui16();
_range = Rangeui16(_range.end, _range.end);
}
}

PointerSet &PointerSet::operator = (PointerSet const &other)
{
auto const bytes = sizeof(Pointer) * other._size;

if (_size != other._size)
{
_size = other._size;
_pointers = reinterpret_cast<Pointer *>(realloc(_pointers, bytes));
}
std::memcpy(_pointers, other._pointers, bytes);
_flags = other._flags;
_range = other._range;
_iterationObserver = other._iterationObserver;
return *this;
}

PointerSet &PointerSet::operator = (PointerSet &&moved)
{
free(_pointers);
Expand All @@ -183,9 +234,38 @@ PointerSet &PointerSet::operator = (PointerSet &&moved)
_flags = moved._flags;
_size = moved._size;
_range = moved._range;

_iterationObserver = moved._iterationObserver;
return *this;
}

void PointerSet::setBeingIterated(bool yes) const
{
duint16 count = _flags & POINTERSET_ITERATION_MASK;
_flags ^= count;
if (yes)
{
DENG2_ASSERT(count != POINTERSET_ITERATION_MASK);
++count;
}
else
{
DENG2_ASSERT(count != 0);
--count;
}
_flags |= count & POINTERSET_ITERATION_MASK;
}

bool PointerSet::isBeingIterated() const
{
return (_flags & POINTERSET_ITERATION_MASK) != 0;
}

void PointerSet::setIterationObserver(IIterationObserver *observer) const
{
_iterationObserver = observer;
}

Rangeui16 PointerSet::locate(Pointer ptr) const
{
// We will narrow down the span until the pointer is found or we'll know where
Expand Down
23 changes: 18 additions & 5 deletions doomsday/tests/test_pointerset/main.cpp
Expand Up @@ -28,7 +28,7 @@ using namespace de;
void printSet(PointerSet const &pset)
{
qDebug() << "[ Size:" << pset.size() << "/" << pset.allocatedSize() << "range:"
<< pset.usedRange().asText();
<< pset.usedRange().asText() << "flags:" << QString::number(pset.flags(), 16);
for (auto const p : pset)
{
qDebug() << " " << QString("%1").arg(reinterpret_cast<dintptr>(p), 16, 16, QChar('0')).toLatin1().constData();
Expand All @@ -45,6 +45,9 @@ int main(int, char **)
PointerSet::Pointer c = reinterpret_cast<PointerSet::Pointer>(0x3000);
PointerSet::Pointer d = reinterpret_cast<PointerSet::Pointer>(0x4000);
PointerSet::Pointer e = reinterpret_cast<PointerSet::Pointer>(0x5000);
// PointerSet::Pointer f = reinterpret_cast<PointerSet::Pointer>(0x6000);
// PointerSet::Pointer g = reinterpret_cast<PointerSet::Pointer>(0x7000);
// PointerSet::Pointer h = reinterpret_cast<PointerSet::Pointer>(0x8000);

PointerSet pset;
qDebug() << "Empty PointerSet:";
Expand All @@ -65,6 +68,11 @@ int main(int, char **)
pset.remove(a);
qDebug() << "Removed the pointer:";
printSet(pset);

qDebug() << "Adding again:";
pset.insert(b);
pset.insert(c);
printSet(pset);

qDebug() << "Adding everything:";
pset.insert(d);
Expand All @@ -84,11 +92,11 @@ int main(int, char **)
printSet(pset);

qDebug() << "Adding everything again:";
pset.insert(e);
pset.insert(d);
pset.insert(a);
pset.insert(c);
pset.insert(b);
pset.insert(e);
pset.insert(a);
printSet(pset);

qDebug() << "Removing everything:";
Expand All @@ -113,7 +121,8 @@ int main(int, char **)
pset.insert(c);
pset.insert(b);
pset.insert(a);
pset.setFlags(PointerSet::BeingIterated);
pset.setBeingIterated(true);
printSet(pset);
for (auto i : pset)
{
if (i == c)
Expand All @@ -133,7 +142,11 @@ int main(int, char **)
}
pset.remove(d);
}
pset.setFlags(PointerSet::BeingIterated, false);
pset.setBeingIterated(false);
printSet(pset);

qDebug() << "Assignment:";
pset = PointerSet();
printSet(pset);
}
catch (Error const &err)
Expand Down

0 comments on commit c0883d1

Please sign in to comment.