Skip to content

Commit

Permalink
libcore: Added a new utility class
Browse files Browse the repository at this point in the history
PointerSet is used for storing a light-weight ordered set of pointers.
  • Loading branch information
skyjake committed Feb 11, 2017
1 parent 93692fc commit f81a6fa
Show file tree
Hide file tree
Showing 7 changed files with 472 additions and 0 deletions.
1 change: 1 addition & 0 deletions doomsday/sdk/libcore/include/de/PointerSet
@@ -0,0 +1 @@
#include "data/pointerset.h"
1 change: 1 addition & 0 deletions doomsday/sdk/libcore/include/de/core/range.h
Expand Up @@ -151,6 +151,7 @@ struct Range
}
};

typedef Range<duint16> Rangeui16;
typedef Range<dint32> Rangei;
typedef Range<duint32> Rangeui;
typedef Range<dint64> Rangei64;
Expand Down
86 changes: 86 additions & 0 deletions doomsday/sdk/libcore/include/de/data/pointerset.h
@@ -0,0 +1,86 @@
/** @file pointerset.h Set of pointers.
*
* @authors Copyright (c) 2017 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* LGPL: http://www.gnu.org/licenses/lgpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your
* option) any later version. This program is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/

#ifndef LIBDENG2_POINTERSET_H
#define LIBDENG2_POINTERSET_H

#include "../libcore.h"
#include "../Range"
#include <vector>

namespace de {

/**
* Set of pointers.
*
* Light-weight class specifically designed to be used for observer audiences. Maintains
* a sorted vector of pointers. Insertions, deletions, and lookups are done with an
* O(log n) binary search. Insertions start at the middle to allow expansion to both
* directions. Removing individual pointers is allowed at any time.
*/
class DENG2_PUBLIC PointerSet
{
public:
typedef void * Pointer;
typedef Pointer const * const_iterator;
typedef duint16 Flag;

static Flag const AllowInsertionDuringIteration;
static Flag const BeingIterated;

public:
PointerSet();
PointerSet(PointerSet const &other);
PointerSet(PointerSet &&moved);

~PointerSet();

void insert(Pointer ptr);
void remove(Pointer ptr);
bool contains(Pointer ptr) const;
void clear();

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; }

protected:
Rangeui16 locate(Pointer ptr) const;

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

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

} // namespace de


#endif // LIBDENG2_POINTERSET_H
232 changes: 232 additions & 0 deletions doomsday/sdk/libcore/src/data/pointerset.cpp
@@ -0,0 +1,232 @@
/** @file pointerset.cpp Set of pointers.
*
* @authors Copyright (c) 2017 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* LGPL: http://www.gnu.org/licenses/lgpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your
* option) any later version. This program is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/

#include "de/PointerSet"
#include <cstdlib>

namespace de {

static duint16 const POINTERSET_MIN_ALLOC = 2;
static duint16 const POINTERSET_MAX_SIZE = 0xffff;

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

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

PointerSet::PointerSet(PointerSet const &other)
: _flags(other._flags)
, _size (other._size)
, _range(other._range)
{
auto const bytes = sizeof(Pointer) * _size;
_pointers = reinterpret_cast<Pointer *>(malloc(bytes));
std::memcpy(_pointers, other._pointers, bytes);
}

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

PointerSet::~PointerSet()
{
free(_pointers);
}

void PointerSet::insert(Pointer ptr)
{
if (!_pointers)
{
// Make a minimum allocation.
_size = POINTERSET_MIN_ALLOC;
_pointers = reinterpret_cast<Pointer *>(calloc(sizeof(Pointer), _size));
}

if (_range.isEmpty())
{
// Nothing is currently allocated. Place the first item in the middle.
duint16 const pos = _size / 2;
_pointers[pos] = ptr;
_range.start = pos;
_range.end = pos + 1;
}
else
{
auto const loc = locate(ptr);
if (!loc.isEmpty()) return; // Already got it.

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

// Do we need to expand?
if (_range.size() == _size)
{
DENG2_ASSERT(_size < POINTERSET_MAX_SIZE);

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));
}

// Addition to the ends with room to spare?
duint16 const pos = loc.start;
if (pos == _range.start && _range.start > 0)
{
_pointers[--_range.start] = ptr;
}
else if (pos == _range.end && _range.end < _size)
{
_pointers[_range.end++] = ptr;
}
else // Need to move first to make room.
{
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)
{
DENG2_ASSERT(_range.end < _size);
std::memmove(_pointers + pos + 1,
_pointers + pos,
sizeof(Pointer) * (_range.end - pos));
_range.end++;
_pointers[pos] = ptr;
}
else
{
std::memmove(_pointers + _range.start - 1,
_pointers + _range.start,
sizeof(Pointer) * (pos - _range.start + 1));
_range.start--;
_pointers[pos - 1] = ptr;
}
}
}
}

void PointerSet::remove(Pointer ptr)
{
auto const loc = locate(ptr);
if (!loc.isEmpty())
{
DENG2_ASSERT(!_range.isEmpty());

// Removing the first or last item needs just a range adjustment.
if (loc.start == _range.start)
{
_pointers[_range.start++] = nullptr;
}
else if (loc.start == _range.end - 1 &&
!(_flags & BeingIterated))
{
_pointers[--_range.end] = nullptr;
}
else
{
// Move forward so that during iteration the future items won't be affected.
std::memmove(_pointers + _range.start + 1,
_pointers + _range.start,
sizeof(Pointer) * (loc.start - _range.start));
_pointers[_range.start++] = nullptr;
}

DENG2_ASSERT(_range.start <= _range.end);
}
}

bool PointerSet::contains(Pointer ptr) const
{
return !locate(ptr).isEmpty();
}

void PointerSet::clear()
{
if (_pointers)
{
std::memset(_pointers, 0, sizeof(Pointer) * _size);
_range = Rangeui16();
}
}

PointerSet &PointerSet::operator = (PointerSet &&moved)
{
free(_pointers);
_pointers = moved._pointers;
moved._pointers = nullptr;

_flags = moved._flags;
_size = moved._size;
_range = moved._range;
return *this;
}

Rangeui16 PointerSet::locate(Pointer ptr) const
{
// We will narrow down the span until the pointer is found or we'll know where
// it would be if it were inserted.
Rangeui16 span = _range;

while (!span.isEmpty())
{
// Arrived at a single item?
if (span.size() == 1)
{
if (at(span.start) == ptr)
{
return span; // Found it.
}
// Then the ptr would go before or after this position.
if (ptr < at(span.start))
{
return Rangeui16(span.start, span.start);
}
return Rangeui16(span.end, span.end);
}

// Narrow down the search by a half.
Rangeui16 const rightHalf((span.start + span.end + 1) / 2, span.end);
Pointer const mid = at(rightHalf.start);
if (ptr == mid)
{
// Oh, it's here.
return Rangeui16(rightHalf.start, rightHalf.start + 1);
}
else if (ptr > mid)
{
span = rightHalf;
}
else
{
span = Rangeui16(span.start, rightHalf.start);
}
}
return span;
}

} // namespace de
1 change: 1 addition & 0 deletions doomsday/tests/CMakeLists.txt
Expand Up @@ -13,6 +13,7 @@ if (DENG_ENABLE_TESTS)
add_subdirectory (test_commandline)
add_subdirectory (test_info)
add_subdirectory (test_log)
add_subdirectory (test_pointerset)
add_subdirectory (test_record)
add_subdirectory (test_script)
add_subdirectory (test_string)
Expand Down
5 changes: 5 additions & 0 deletions doomsday/tests/test_pointerset/CMakeLists.txt
@@ -0,0 +1,5 @@
cmake_minimum_required (VERSION 3.1)
project (DENG_TEST_POINTERSET)
include (../TestConfig.cmake)

deng_test (test_pointerset main.cpp)

0 comments on commit f81a6fa

Please sign in to comment.