#pragma once #include #include #include 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 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 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 class LinkArray : LinkContainer {}; template class LinkBucket : LinkContainer {}; /** * 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 class LinkMapBase : LinkContainer { 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 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 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 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 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(key, value, dirtyResolution); } void setDouble(uint64_t key, double value, double dirtyResolution = 0.0) { setTyped(key, reinterpret_cast(value), dirtyResolution); } }; typedef LinkMapBase<> LinkMap;