602 lines
13 KiB
C++
602 lines
13 KiB
C++
#pragma once
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
enum LinkContainerBehavior {
|
|
LCB_Unordered,
|
|
LCB_Ordered,
|
|
};
|
|
|
|
/* LinkContainer
|
|
* -------------
|
|
* Base type for a set of containers that are thread-safe to read from.
|
|
* Reads can occur at the same time as writes, but writes must not happen simultaneously!
|
|
*
|
|
* Intended for use with small data structures that need to be read from multiple threads,
|
|
* and can change but do so infrequently enough that the extra write overhead is negligible.
|
|
*
|
|
* CAVEATS:
|
|
* - Will never shrink in size. Seriously, only use for small sets.
|
|
* - Iterating over it is sometimes going to skip an element.
|
|
* - Getting by index is sometimes going to return null even if index < size.
|
|
* */
|
|
template<typename T, unsigned char PoolSize, LinkContainerBehavior Behavior>
|
|
class LinkContainer {
|
|
protected:
|
|
void* start;
|
|
unsigned int count;
|
|
|
|
struct PoolHeader {
|
|
unsigned char filledElements;
|
|
bool contiguous;
|
|
void* next;
|
|
};
|
|
|
|
struct PoolElem {
|
|
bool filled;
|
|
T data;
|
|
};
|
|
|
|
inline PoolHeader& getHeader(void* mem) const {
|
|
return *(PoolHeader*)mem;
|
|
}
|
|
|
|
inline PoolElem& getElem(void* mem, unsigned char index) const {
|
|
unsigned char* dataStart = ((unsigned char*)mem) + sizeof(PoolHeader);
|
|
unsigned char* elemData = dataStart + (sizeof(PoolElem) * index);
|
|
return *(PoolElem*)elemData;
|
|
}
|
|
|
|
inline PoolElem& getElem(void* at) const
|
|
{
|
|
return *(PoolElem*)at;
|
|
}
|
|
|
|
inline void* allocate(const T& data) {
|
|
auto size = sizeof(PoolHeader) + sizeof(PoolElem) * PoolSize;
|
|
void* pool = malloc(size);
|
|
memset(pool, 0, size);
|
|
auto& header = getHeader(pool);
|
|
header.filledElements = 1;
|
|
header.contiguous = true;
|
|
auto& elem = getElem(pool, 0);
|
|
elem.filled = true;
|
|
elem.data = data;
|
|
return pool;
|
|
}
|
|
|
|
inline void* getLast() const {
|
|
void* pool = start;
|
|
while(pool)
|
|
{
|
|
auto& header = getHeader(pool);
|
|
if(header.next)
|
|
pool = header.next;
|
|
else
|
|
return pool;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void checkContiguous(void* pool) {
|
|
auto& header = getHeader(pool);
|
|
bool foundEmpty = false;
|
|
for(unsigned char i = 0; i < header.filledElements; ++i) {
|
|
if(!getElem(pool, i).filled) {
|
|
foundEmpty = true;
|
|
break;
|
|
}
|
|
}
|
|
header.contiguous = !foundEmpty;
|
|
}
|
|
|
|
void getIndex(unsigned int index, void*& outPool, unsigned char& outElem) const {
|
|
void* pool = start;
|
|
while(pool) {
|
|
auto& header = getHeader(pool);
|
|
unsigned char cnt = header.filledElements;
|
|
if(index < cnt) {
|
|
if(header.contiguous)
|
|
{
|
|
auto& elem = getElem(pool, index);
|
|
if(elem.filled)
|
|
{
|
|
outPool = pool;
|
|
outElem = index;
|
|
return;
|
|
}
|
|
}
|
|
|
|
for(unsigned char i = 0; i < PoolSize; ++i)
|
|
{
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled) {
|
|
if(index == 0) {
|
|
outPool = pool;
|
|
outElem = i;
|
|
return;
|
|
}
|
|
else {
|
|
index--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
index -= cnt;
|
|
}
|
|
pool = header.next;
|
|
}
|
|
outPool = nullptr;
|
|
outElem = -1;
|
|
}
|
|
|
|
public:
|
|
LinkContainer()
|
|
: start(nullptr), count(0) {
|
|
}
|
|
|
|
~LinkContainer() {
|
|
auto* pool = start;
|
|
while(pool) {
|
|
void* next = getHeader(pool).next;
|
|
free(pool);
|
|
pool = next;
|
|
}
|
|
|
|
start = nullptr;
|
|
count = 0;
|
|
}
|
|
|
|
/* Add a new element to the container. If Behavior was set to Ordered, this
|
|
* will insert at the end. If not, it will insert at an arbitrary point in
|
|
* the container. */
|
|
void add(const T& data) {
|
|
// Simple case, we are empty
|
|
if(start == nullptr) {
|
|
start = allocate(data);
|
|
count++;
|
|
return;
|
|
}
|
|
|
|
// Go through pools and see what to do
|
|
if(Behavior == LCB_Unordered) {
|
|
// See if we have an existing pool we can insert into
|
|
void* pool = start;
|
|
while (pool) {
|
|
auto& header = getHeader(pool);
|
|
if(header.filledElements < PoolSize) {
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(!elem.filled) {
|
|
header.filledElements++;
|
|
elem.data = data;
|
|
elem.filled = true;
|
|
count++;
|
|
if(!header.contiguous) {
|
|
if(header.filledElements == PoolSize || header.filledElements == i+1)
|
|
header.contiguous = true;
|
|
else
|
|
checkContiguous(pool);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(header.next) {
|
|
pool = header.next;
|
|
}
|
|
else {
|
|
// Create a new pool at the end
|
|
header.next = allocate(data);
|
|
count++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else /*if(Behavior == LCB_Ordered)*/
|
|
{
|
|
// We can only insert into the last pool we have, otherwise we need to create a new one
|
|
auto* pool = getLast();
|
|
auto& header = getHeader(pool);
|
|
if(header.filledElements < PoolSize) {
|
|
unsigned char lastFilled = (unsigned char)-1;
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, PoolSize - i - 1);
|
|
if(elem.filled) {
|
|
lastFilled = PoolSize - i - 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(lastFilled + 1 >= PoolSize) {
|
|
// Must create a new pool
|
|
header.next = allocate(data);
|
|
count++;
|
|
}
|
|
else {
|
|
// Insert one past the last filled element
|
|
auto& elem = getElem(pool, lastFilled + 1);
|
|
header.filledElements++;
|
|
elem.data = data;
|
|
elem.filled = true;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool contains(const T& data) const {
|
|
void* pool = start;
|
|
while(pool) {
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled && elem.data == data)
|
|
return true;
|
|
}
|
|
pool = getHeader(pool).next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void removeAt(unsigned int index) {
|
|
void* pool;
|
|
unsigned char elem;
|
|
getIndex(index, pool, elem);
|
|
|
|
if(pool == nullptr || elem < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
getElem(pool, elem).filled = false;
|
|
getHeader(pool).filledElements--;
|
|
count--;
|
|
checkContiguous(pool);
|
|
}
|
|
|
|
int removeAll(const T& value) {
|
|
int removedCount = 0;
|
|
void* pool = start;
|
|
while (pool) {
|
|
auto& header = getHeader(pool);
|
|
bool removed = false;
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled) {
|
|
if(elem.data == value) {
|
|
elem.filled = false;
|
|
removed = true;
|
|
header.filledElements--;
|
|
count--;
|
|
removedCount++;
|
|
}
|
|
}
|
|
}
|
|
if(removed)
|
|
checkContiguous(pool);
|
|
pool = header.next;
|
|
}
|
|
return removedCount;
|
|
}
|
|
|
|
void clear() {
|
|
void* pool = start;
|
|
while (pool) {
|
|
auto& header = getHeader(pool);
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled) {
|
|
header.contiguous = false;
|
|
elem.filled = false;
|
|
header.filledElements--;
|
|
count--;
|
|
}
|
|
}
|
|
pool = header.next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Note that due to the threaded nature of this data structure,
|
|
* this may very well return a nullptr even if index < count,
|
|
* so make sure to always check.
|
|
*
|
|
* It can also of course return a stale (already removed) value,
|
|
* and iteration might temporarily miss an element that was there before.
|
|
*
|
|
* The data structure is guaranteed not to segfault from threaded use, but
|
|
* value may be slightly wrong sometimes.
|
|
*/
|
|
T* getAt(unsigned int index) const {
|
|
void* pool;
|
|
unsigned char elem;
|
|
getIndex(index, pool, elem);
|
|
if(pool != nullptr && elem >= 0)
|
|
{
|
|
return &getElem(pool, elem).data;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
unsigned int size() const {
|
|
return count;
|
|
}
|
|
|
|
template<typename CB>
|
|
void iterateAll(CB cb) const {
|
|
void* pool = start;
|
|
while(pool) {
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled)
|
|
cb(elem.data);
|
|
}
|
|
pool = getHeader(pool).next;
|
|
}
|
|
}
|
|
};
|
|
|
|
template<typename T, int PoolSize = 16>
|
|
class LinkArray : LinkContainer<T, PoolSize, LCB_Ordered> {};
|
|
|
|
template<typename T, int PoolSize = 16>
|
|
class LinkBucket : LinkContainer<T, PoolSize, LCB_Unordered> {};
|
|
|
|
/**
|
|
* A very simple O(n) map structure for int64 -> int64/double.
|
|
*
|
|
* Should be used for very small maps when the set of keys changes rarely, and
|
|
* threaded reading of key/value pairs is worth the key lookup and change
|
|
* overhead.
|
|
*
|
|
* Also directly supports value delta tracking.
|
|
*/
|
|
struct LinkMapElem {
|
|
uint64_t key;
|
|
bool dirty;
|
|
union {
|
|
uint64_t value;
|
|
double doubleValue;
|
|
};
|
|
union {
|
|
uint64_t prevValue;
|
|
double prevDoubleValue;
|
|
};
|
|
};
|
|
|
|
template<int PoolSize = 16>
|
|
class LinkMapBase : LinkContainer<struct LinkMapElem, 16, LCB_Unordered> {
|
|
private:
|
|
unsigned int dirtyCount;
|
|
union {
|
|
uint64_t defaultInt;
|
|
double defaultDouble;
|
|
};
|
|
|
|
inline LinkMapElem* getMapElem(uint64_t key) const {
|
|
void* pool = start;
|
|
while(pool) {
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled && elem.data.key == key)
|
|
return &elem.data;
|
|
}
|
|
pool = getHeader(pool).next;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
template<typename T, bool isDouble>
|
|
inline void setTyped(uint64_t key, uint64_t value, T dirtyResolution) {
|
|
void* emptyElem = nullptr;
|
|
void* emptyPool = nullptr;
|
|
void* pool = start;
|
|
while(pool) {
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled) {
|
|
if(elem.data.key == key) {
|
|
if(!elem.data.dirty && dirtyResolution >= 0) {
|
|
if(dirtyResolution == 0) {
|
|
elem.data.dirty = true;
|
|
}
|
|
else if(isDouble) {
|
|
elem.data.value = value;
|
|
if(fabs(elem.data.doubleValue - elem.data.prevDoubleValue) >= dirtyResolution) {
|
|
elem.data.dirty = true;
|
|
dirtyCount++;
|
|
}
|
|
}
|
|
else {
|
|
elem.data.value = value;
|
|
if(llabs((int64_t)elem.data.value - (int64_t)elem.data.prevValue) >= dirtyResolution) {
|
|
elem.data.dirty = true;
|
|
dirtyCount++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
elem.data.value = value;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
emptyElem = &elem;
|
|
emptyPool = pool;
|
|
}
|
|
}
|
|
pool = getHeader(pool).next;
|
|
}
|
|
|
|
if(emptyElem) {
|
|
auto& header = getHeader(emptyPool);
|
|
header.filledElements++;
|
|
auto& elem = getElem(emptyElem);
|
|
elem.data.key = key;
|
|
elem.data.dirty = true;
|
|
elem.data.value = value;
|
|
elem.data.prevValue = value;
|
|
elem.filled = true;
|
|
|
|
count++;
|
|
dirtyCount++;
|
|
}
|
|
else {
|
|
auto* pool = getLast();
|
|
|
|
LinkMapElem newElem;
|
|
newElem.key = key;
|
|
newElem.dirty = true;
|
|
newElem.value = value;
|
|
newElem.prevValue = value;
|
|
if(pool)
|
|
getHeader(pool).next = allocate(newElem);
|
|
else
|
|
start = allocate(newElem);
|
|
|
|
count++;
|
|
dirtyCount++;
|
|
}
|
|
}
|
|
public:
|
|
LinkMapBase() : defaultInt(0), dirtyCount(0) {
|
|
}
|
|
|
|
LinkMapBase(uint64_t defaultValue) : defaultInt(defaultValue), dirtyCount(0) {
|
|
}
|
|
|
|
LinkMapBase(double defaultValue) : defaultDouble(defaultValue), dirtyCount(0) {
|
|
}
|
|
|
|
void setDefaultValue(uint64_t newValue) {
|
|
defaultInt = newValue;
|
|
}
|
|
|
|
uint64_t getDefaultValue() const {
|
|
return defaultInt;
|
|
}
|
|
|
|
void setDefaultDouble(double newValue) {
|
|
defaultDouble = newValue;
|
|
}
|
|
|
|
double getDefaultDouble() const {
|
|
return defaultDouble;
|
|
}
|
|
|
|
unsigned int size() const {
|
|
return count;
|
|
}
|
|
|
|
uint64_t getKeyAtIndex(unsigned int index) const {
|
|
if(auto* elem = getAt(index))
|
|
return elem->key;
|
|
return -1;
|
|
}
|
|
|
|
uint64_t getAtIndex(unsigned int index) const {
|
|
if(auto* elem = getAt(index))
|
|
return elem->value;
|
|
return defaultInt;
|
|
}
|
|
|
|
double getDoubleAtIndex(unsigned int index) const {
|
|
if(auto* elem = getAt(index))
|
|
return elem->doubleValue;
|
|
return defaultDouble;
|
|
}
|
|
|
|
uint64_t get(uint64_t key) const {
|
|
auto* elem = getMapElem(key);
|
|
if(elem)
|
|
return elem->value;
|
|
else
|
|
return defaultInt;
|
|
}
|
|
|
|
double getDouble(uint64_t key) const {
|
|
auto* elem = getMapElem(key);
|
|
if(elem)
|
|
return elem->doubleValue;
|
|
else
|
|
return defaultDouble;
|
|
}
|
|
|
|
template<typename CB>
|
|
void iterateAll(CB cb) const {
|
|
void* pool = start;
|
|
while(pool) {
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled)
|
|
cb(elem.data.key, elem.data.value);
|
|
}
|
|
pool = getHeader(pool).next;
|
|
}
|
|
}
|
|
|
|
template<typename CB>
|
|
void iterateDirty(CB cb) const {
|
|
void* pool = start;
|
|
while(pool) {
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled && elem.data.dirty)
|
|
cb(elem.data.key, elem.data.value);
|
|
}
|
|
pool = getHeader(pool).next;
|
|
}
|
|
}
|
|
|
|
template<typename CB>
|
|
void handleDirty(CB cb) {
|
|
void* pool = start;
|
|
while(pool) {
|
|
for(unsigned char i = 0; i < PoolSize; ++i) {
|
|
auto& elem = getElem(pool, i);
|
|
if(elem.filled && elem.data.dirty) {
|
|
if(cb(elem.data.key, elem.data.value)) {
|
|
elem.data.prevValue = elem.data.value;
|
|
elem.data.dirty = false;
|
|
dirtyCount--;
|
|
}
|
|
}
|
|
}
|
|
pool = getHeader(pool).next;
|
|
}
|
|
}
|
|
|
|
bool getDirtyCount() const {
|
|
return dirtyCount;
|
|
}
|
|
|
|
bool hasDirty() const {
|
|
return dirtyCount != 0;
|
|
}
|
|
|
|
bool isDirty(uint64_t key) const {
|
|
auto* elem = getMapElem(key);
|
|
return elem != nullptr && elem->dirty;
|
|
}
|
|
|
|
bool contains(uint64_t key) const {
|
|
return getMapElem(key) != nullptr;
|
|
}
|
|
|
|
void set(uint64_t key, uint64_t value, int64_t dirtyResolution = 0) {
|
|
setTyped<int64_t,true>(key, value, dirtyResolution);
|
|
}
|
|
|
|
void setDouble(uint64_t key, double value, double dirtyResolution = 0.0) {
|
|
setTyped<double,true>(key, reinterpret_cast<uint64_t&>(value), dirtyResolution);
|
|
}
|
|
};
|
|
|
|
typedef LinkMapBase<> LinkMap;
|