#include #include #include #include // Block-based dynamic allocator via ring-buffer. // Ring buffer that stores "blocks" which dynamically grow and wrap. // If a section grows large enough that it overlaps another section, the other section is forgotten. // 8 bit alignment, bring your own synchronization. // Must call Create first, and Destroy when done. Must create a section before writing. // This probably isn't perfect.. some tests are at the bottom and it seems to be stable for the usecase. struct RingBuf { enum OverflowMode { kOverflowModeWrap, kOverflowModeTruncate, kOverflowModeGrowDouble, }; uint8_t* data; uint32_t cacheSize; OverflowMode overflowMode; // must be pointer because of thread_local compiler bug std::deque* offsets; void Create(uint32_t csize, OverflowMode overflowMode) { this->overflowMode = overflowMode; if (data == nullptr) { data = (uint8_t*)malloc(csize); cacheSize = csize; offsets = new std::deque(); } Reset(); } void SetOverflowMode(OverflowMode overflowMode) { this->overflowMode = overflowMode; } void Reset() { if (offsets != nullptr) { offsets->clear(); offsets->push_back(0); } } void Destroy() { if (data != nullptr) { free(data); data = nullptr; delete (offsets); cacheSize = 0; } } void CreateNewBlock() { if (offsets != nullptr) offsets->push_back(offsets->back()); } void DropLastBlock() { if (offsets != nullptr) offsets->pop_back(); } uint8_t* GetForWrite(uint32_t size) { uint8_t* ret{nullptr}; if (offsets == nullptr) return nullptr; if (size > cacheSize && overflowMode == kOverflowModeWrap) return nullptr; if (offsets->size() < 2) return nullptr; uint32_t head = offsets->front(); uint32_t tail = offsets->back(); offsets->pop_back(); // need to wrap if (tail + size > cacheSize) { if (overflowMode == kOverflowModeWrap) { // section grew larger than full buffer and overwrote itself, abort. if (offsets->size() == 1) return nullptr; if (offsets->back() != tail) offsets->push_back(tail); uint32_t front = offsets->front(); while (front != 0) { offsets->pop_front(); front = offsets->front(); } offsets->pop_front(); offsets->push_back(0); head = offsets->front(); tail = offsets->back(); } else if (overflowMode == kOverflowModeGrowDouble) { while (tail + size > cacheSize) { // grow uint32_t newCacheSize = cacheSize * 2; uint8_t* newData = (uint8_t*)malloc(newCacheSize); memcpy(newData, data, cacheSize); free(data); data = newData; cacheSize = newCacheSize; } } else if (overflowMode == kOverflowModeTruncate) { return nullptr; } else { assert(0); } } // need to free space / forget sections if (tail <= head && head != 0) { while (tail + size > head) { offsets->pop_front(); head = offsets->front(); tail = offsets->back(); if (offsets->size() == 1) break; if (*(offsets->begin() + 1) == 0) { offsets->pop_front(); break; } } } offsets->push_back(tail + size); ret = &data[tail]; return ret; } bool HasDataForRead() { return offsets != nullptr && offsets->size() > 1 && offsets[0] != offsets[1]; } // returns true if there is more data to read // in practice there may be two reads necessary to get all the data, from mid to end and from beginning to mid. // reading is a one time operation - data is cleared after read. bool GetForReadAndClear(uint8_t** ptr, uint32_t* size) { if (offsets == nullptr) { *ptr = nullptr; *size = 0; return false; } uint32_t head = offsets->front(); *ptr = &data[head]; uint32_t max = 0; for (auto it = offsets->begin(); it != offsets->end();) { if (*it > max) max = *it; else if (max == 0) { ++it; continue; } else break; it = offsets->erase(it); } *size = max - head; return offsets->size() > 1 && size != 0; } bool GetForRead(uint8_t** ptr, uint32_t* size) { if (overflowMode == kOverflowModeGrowDouble && offsets != nullptr) { *ptr = data; *size = offsets->back(); return false; } *ptr = nullptr; *size = 0; return false; } bool MoveFrom(RingBuf& other) { if (other.HasDataForRead() && offsets != nullptr) { CreateNewBlock(); uint8_t* ptr{}; uint32_t size{}; bool more = false; do { more = other.GetForReadAndClear(&ptr, &size); uint8_t* dst = GetForWrite(size); if (dst != nullptr) { memcpy(dst, ptr, size); } } while (more); } else { return false; } return true; } void Write(Command c) { uint8_t* wbuf = GetForWrite(sizeof(Command)); if (wbuf != nullptr) { *(Command*)wbuf = c; } } void Write(LUT l) { uint8_t* wbuf = GetForWrite(sizeof(LUT)); if (wbuf != nullptr) { *(LUT*)wbuf = l; } } void Write(std::thread::id id) { std::stringstream ss; ss << id; Write(ss.str()); } void Write(const std::string& s) { uint8_t* wbuf = GetForWrite(sizeof(char) * ((uint32_t)s.size() + 1)); if (wbuf != nullptr) { memcpy(wbuf, s.c_str(), sizeof(char) * (uint32_t)s.size()); wbuf[s.size()] = 0; } } void Write(float f) { uint8_t* wbuf = GetForWrite(sizeof(float)); if (wbuf != nullptr) { *(float*)wbuf = f; } } void Write(int32_t i) { uint8_t* wbuf = GetForWrite(sizeof(int32_t)); if (wbuf != nullptr) { *(int32_t*)wbuf = i; } } void Write(int64_t i) { uint8_t* wbuf = GetForWrite(sizeof(int64_t)); if (wbuf != nullptr) { *(int64_t*)wbuf = i; } } void Write(uint32_t u) { uint8_t* wbuf = GetForWrite(sizeof(uint32_t)); if (wbuf != nullptr) { *(uint32_t*)wbuf = u; } } void Write(uint64_t u) { uint8_t* wbuf = GetForWrite(sizeof(uint64_t)); if (wbuf != nullptr) { *(uint64_t*)wbuf = u; } } }; //#define CACHE_SIZE 1024 * 1024 * 1 // //int tests() //{ // RingBuf buf{}; // buf.Create(CACHE_SIZE); // // uint8_t* ptr; // uint32_t size; // // // Check that starts out empty // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == 0); // buf.Reset(); // // // Write // buf.CreateNewBlock(); // auto* wbuf = buf.GetForWrite(4); // *(uint32_t*)wbuf = 0x12345678; // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == 4); // assert(*(uint32_t*)ptr == 0x12345678); // buf.Reset(); // // // Write 2 // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(4); // *(uint32_t*)wbuf = 0x12345678; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // *(uint64_t*)wbuf = 0x123456789abcdef0; // // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == 12); // assert(*(uint32_t*)ptr == 0x12345678); // assert(*(uint64_t*)(ptr + 4) == 0x123456789abcdef0); // buf.Reset(); // // // Full buf // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(CACHE_SIZE - 16); // // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == CACHE_SIZE); // buf.Reset(); // // // Wrap buf, perfectly lines up // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 1; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 2; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(CACHE_SIZE - 16); // for (int i = 0; i < (CACHE_SIZE - 16); ++i) // wbuf[i] = 3; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 4; // // assert(buf.GetForReadAndClear(&ptr, &size) == true); // assert(size == CACHE_SIZE - 8); // assert(ptr[0] == 2); // assert(ptr[8] == 3); // assert(ptr[size - 1] == 3); // // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == 8); // assert(ptr[0] == 4); // buf.Reset(); // // // Wrap buf, not perfect // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 1; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 2; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(CACHE_SIZE - 20); // for (int i = 0; i < (CACHE_SIZE - 20); ++i) // wbuf[i] = 3; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 4; // // assert(buf.GetForReadAndClear(&ptr, &size) == true); // assert(size == CACHE_SIZE - 12); // assert(ptr[0] == 2); // assert(ptr[8] == 3); // assert(ptr[size - 1] == 3); // // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == 8); // assert(ptr[0] == 4); // buf.Reset(); // // // Wrap, and fit more in previous first spot // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 1; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 2; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(CACHE_SIZE - 16); // for (int i = 0; i < (CACHE_SIZE - 16); ++i) // wbuf[i] = 3; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(4); // for (int i = 0; i < 4; ++i) // wbuf[i] = 4; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(4); // for (int i = 0; i < 4; ++i) // wbuf[i] = 5; // // assert(buf.GetForReadAndClear(&ptr, &size) == true); // assert(size == CACHE_SIZE - 8); // assert(ptr[0] == 2); // assert(ptr[8] == 3); // assert(ptr[size - 1] == 3); // // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == 8); // assert(ptr[0] == 4); // assert(ptr[4] == 5); // buf.Reset(); // // // Wrap, and consume first two entries // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 1; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 2; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(CACHE_SIZE - 16); // for (int i = 0; i < (CACHE_SIZE - 16); ++i) // wbuf[i] = 3; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(4); // for (int i = 0; i < 4; ++i) // wbuf[i] = 4; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 5; // // assert(buf.GetForReadAndClear(&ptr, &size) == true); // assert(size == CACHE_SIZE - 8 - 8); // assert(ptr[0] == 3); // assert(ptr[size - 1] == 3); // // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == 12); // assert(ptr[0] == 4); // assert(ptr[4] == 5); // buf.Reset(); // // // Wrap, and consume first three entries // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 1; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 2; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(CACHE_SIZE - 16); // for (int i = 0; i < (CACHE_SIZE - 16); ++i) // wbuf[i] = 3; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(4); // for (int i = 0; i < 4; ++i) // wbuf[i] = 4; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 5; // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 6; // // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == 20); // assert(ptr[0] == 4); // assert(ptr[4] == 5); // assert(ptr[12] == 6); // buf.Reset(); // // // Section wraps, perfectly // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 1; // buf.CreateNewBlock(); // buf.GetForWrite(CACHE_SIZE); // for (int i = 0; i < (CACHE_SIZE); ++i) // wbuf[i] = 2; // // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == CACHE_SIZE); // assert(ptr[0] == 2); // assert(ptr[CACHE_SIZE - 1] == 2); // buf.Reset(); // // // Section wraps, not perfect // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 1; // buf.CreateNewBlock(); // buf.GetForWrite(CACHE_SIZE - 16); // for (int i = 0; i < (CACHE_SIZE - 16); ++i) // wbuf[i] = 2; // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 2; // wbuf = buf.GetForWrite(8); // for (int i = 0; i < 8; ++i) // wbuf[i] = 2; // // assert(buf.GetForReadAndClear(&ptr, &size) == true); // assert(size == CACHE_SIZE - 8); // assert(ptr[0] == 2); // assert(ptr[size - 1] == 2); // // assert(buf.GetForReadAndClear(&ptr, &size) == false); // assert(size == 8); // assert(ptr[0] == 2); // buf.Reset(); // // // section wraps on itself // buf.CreateNewBlock(); // wbuf = buf.GetForWrite(CACHE_SIZE - 8); // wbuf = buf.GetForWrite(8); // // wbuf = buf.GetForWrite(8); // assert(wbuf == nullptr); // // return 0; //}