/*
* Copyright 2006 The Android Open Source Project
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkDeque.h"
struct SkDeque::Block {
Block* fNext;
Block* fPrev;
char* fBegin; // start of used section in this chunk
char* fEnd; // end of used section in this chunk
char* fStop; // end of the allocated chunk
char* start() { return (char*)(this + 1); }
const char* start() const { return (const char*)(this + 1); }
void init(size_t size) {
fNext = fPrev = nullptr;
fBegin = fEnd = nullptr;
fStop = (char*)this + size;
}
};
SkDeque::SkDeque(size_t elemSize, int allocCount)
: fElemSize(elemSize)
, fInitialStorage(nullptr)
, fCount(0)
, fAllocCount(allocCount) {
SkASSERT(allocCount >= 1);
fFrontBlock = fBackBlock = nullptr;
fFront = fBack = nullptr;
}
SkDeque::SkDeque(size_t elemSize, void* storage, size_t storageSize, int allocCount)
: fElemSize(elemSize)
, fInitialStorage(storage)
, fCount(0)
, fAllocCount(allocCount) {
SkASSERT(storageSize == 0 || storage != nullptr);
SkASSERT(allocCount >= 1);
if (storageSize >= sizeof(Block) + elemSize) {
fFrontBlock = (Block*)storage;
fFrontBlock->init(storageSize);
} else {
fFrontBlock = nullptr;
}
fBackBlock = fFrontBlock;
fFront = fBack = nullptr;
}
SkDeque::~SkDeque() {
Block* head = fFrontBlock;
Block* initialHead = (Block*)fInitialStorage;
while (head) {
Block* next = head->fNext;
if (head != initialHead) {
this->freeBlock(head);
}
head = next;
}
}
void* SkDeque::push_front() {
fCount += 1;
if (nullptr == fFrontBlock) {
fFrontBlock = this->allocateBlock(fAllocCount);
fBackBlock = fFrontBlock; // update our linklist
}
Block* first = fFrontBlock;
char* begin;
if (nullptr == first->fBegin) {
INIT_CHUNK:
first->fEnd = first->fStop;
begin = first->fStop - fElemSize;
} else {
begin = first->fBegin - fElemSize;
if (begin < first->start()) { // no more room in this chunk
// should we alloc more as we accumulate more elements?
first = this->allocateBlock(fAllocCount);
first->fNext = fFrontBlock;
fFrontBlock->fPrev = first;
fFrontBlock = first;
goto INIT_CHUNK;
}
}
first->fBegin = begin;
if (nullptr == fFront) {
SkASSERT(nullptr == fBack);
fFront = fBack = begin;
} else {
SkASSERT(fBack);
fFront = begin;
}
return begin;
}
void* SkDeque::push_back() {
fCount += 1;
if (nullptr == fBackBlock) {
fBackBlock = this->allocateBlock(fAllocCount);
fFrontBlock = fBackBlock; // update our linklist
}
Block* last = fBackBlock;
char* end;
if (nullptr == last->fBegin) {
INIT_CHUNK:
last->fBegin = last->start();
end = last->fBegin + fElemSize;
} else {
end = last->fEnd + fElemSize;
if (end > last->fStop) { // no more room in this chunk
// should we alloc more as we accumulate more elements?
last = this->allocateBlock(fAllocCount);
last->fPrev = fBackBlock;
fBackBlock->fNext = last;
fBackBlock = last;
goto INIT_CHUNK;
}
}
last->fEnd = end;
end -= fElemSize;
if (nullptr == fBack) {
SkASSERT(nullptr == fFront);
fFront = fBack = end;
} else {
SkASSERT(fFront);
fBack = end;
}
return end;
}
void SkDeque::pop_front() {
SkASSERT(fCount > 0);
fCount -= 1;
Block* first = fFrontBlock;
SkASSERT(first != nullptr);
if (first->fBegin == nullptr) { // we were marked empty from before
first = first->fNext;
first->fPrev = nullptr;
this->freeBlock(fFrontBlock);
fFrontBlock = first;
SkASSERT(first != nullptr); // else we popped too far
}
char* begin = first->fBegin + fElemSize;
SkASSERT(begin <= first->fEnd);
if (begin < fFrontBlock->fEnd) {
first->fBegin = begin;
SkASSERT(first->fBegin);
fFront = first->fBegin;
} else {
first->fBegin = first->fEnd = nullptr; // mark as empty
if (nullptr == first->fNext) {
fFront = fBack = nullptr;
} else {
SkASSERT(first->fNext->fBegin);
fFront = first->fNext->fBegin;
}
}
}
void SkDeque::pop_back() {
SkASSERT(fCount > 0);
fCount -= 1;
Block* last = fBackBlock;
SkASSERT(last != nullptr);
if (last->fEnd == nullptr) { // we were marked empty from before
last = last->fPrev;
last->fNext = nullptr;
this->freeBlock(fBackBlock);
fBackBlock = last;
SkASSERT(last != nullptr); // else we popped too far
}
char* end = last->fEnd - fElemSize;
SkASSERT(end >= last->fBegin);
if (end > last->fBegin) {
last->fEnd = end;
SkASSERT(last->fEnd);
fBack = last->fEnd - fElemSize;
} else {
last->fBegin = last->fEnd = nullptr; // mark as empty
if (nullptr == last->fPrev) {
fFront = fBack = nullptr;
} else {
SkASSERT(last->fPrev->fEnd);
fBack = last->fPrev->fEnd - fElemSize;
}
}
}
int SkDeque::numBlocksAllocated() const {
int numBlocks = 0;
for (const Block* temp = fFrontBlock; temp; temp = temp->fNext) {
++numBlocks;
}
return numBlocks;
}
SkDeque::Block* SkDeque::allocateBlock(int allocCount) {
Block* newBlock = (Block*)sk_malloc_throw(sizeof(Block) + allocCount * fElemSize);
newBlock->init(sizeof(Block) + allocCount * fElemSize);
return newBlock;
}
void SkDeque::freeBlock(Block* block) {
sk_free(block);
}
///////////////////////////////////////////////////////////////////////////////
SkDeque::Iter::Iter() : fCurBlock(nullptr), fPos(nullptr), fElemSize(0) {}
SkDeque::Iter::Iter(const SkDeque& d, IterStart startLoc) {
this->reset(d, startLoc);
}
// Due to how reset and next work, next actually returns the current element
// pointed to by fPos and then updates fPos to point to the next one.
void* SkDeque::Iter::next() {
char* pos = fPos;
if (pos) { // if we were valid, try to move to the next setting
char* next = pos + fElemSize;
SkASSERT(next <= fCurBlock->fEnd);
if (next == fCurBlock->fEnd) { // exhausted this chunk, move to next
do {
fCurBlock = fCurBlock->fNext;
} while (fCurBlock != nullptr && fCurBlock->fBegin == nullptr);
next = fCurBlock ? fCurBlock->fBegin : nullptr;
}
fPos = next;
}
return pos;
}
// Like next, prev actually returns the current element pointed to by fPos and
// then makes fPos point to the previous element.
void* SkDeque::Iter::prev() {
char* pos = fPos;
if (pos) { // if we were valid, try to move to the prior setting
char* prev = pos - fElemSize;
SkASSERT(prev >= fCurBlock->fBegin - fElemSize);
if (prev < fCurBlock->fBegin) { // exhausted this chunk, move to prior
do {
fCurBlock = fCurBlock->fPrev;
} while (fCurBlock != nullptr && fCurBlock->fEnd == nullptr);
prev = fCurBlock ? fCurBlock->fEnd - fElemSize : nullptr;
}
fPos = prev;
}
return pos;
}
// reset works by skipping through the spare blocks at the start (or end)
// of the doubly linked list until a non-empty one is found. The fPos
// member is then set to the first (or last) element in the block. If
// there are no elements in the deque both fCurBlock and fPos will come
// out of this routine nullptr.
void SkDeque::Iter::reset(const SkDeque& d, IterStart startLoc) {
fElemSize = d.fElemSize;
if (kFront_IterStart == startLoc) {
// initialize the iterator to start at the front
fCurBlock = d.fFrontBlock;
while (fCurBlock && nullptr == fCurBlock->fBegin) {
fCurBlock = fCurBlock->fNext;
}
fPos = fCurBlock ? fCurBlock->fBegin : nullptr;
} else {
// initialize the iterator to start at the back
fCurBlock = d.fBackBlock;
while (fCurBlock && nullptr == fCurBlock->fEnd) {
fCurBlock = fCurBlock->fPrev;
}
fPos = fCurBlock ? fCurBlock->fEnd - fElemSize : nullptr;
}
}