blob: c42d9e975181f0ecf55d87d681641dde63b65d92 [file] [log] [blame]
/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrTRecorder_DEFINED
#define GrTRecorder_DEFINED
#include "SkTypes.h"
template<typename TBase, typename TAlign> class GrTRecorder;
template<typename TItem> struct GrTRecorderAllocWrapper;
/**
* Records a list of items with a common base type, optional associated data, and
* permanent memory addresses.
*
* This class preallocates its own chunks of memory for hosting objects, so new items can
* be created without excessive calls to malloc().
*
* To create a new item and append it to the back of the list, use the following macros:
*
* GrNEW_APPEND_TO_RECORDER(recorder, SubclassName, (args))
* GrNEW_APPEND_WITH_DATA_TO_RECORDER(recorder, SubclassName, (args), sizeOfData)
*
* Upon reset or delete, the items are destructed in the same order they were received,
* not reverse (stack) order.
*
* @param TBase Common base type of items in the list. If TBase is not a class with a
* virtual destructor, the client is responsible for invoking any necessary
* destructors.
*
* For now, any subclass used in the list must have the same start address
* as TBase (or in other words, the types must be convertible via
* reinterpret_cast<>). Classes with multiple inheritance (or any subclass
* on an obscure compiler) may not be compatible. This is runtime asserted
* in debug builds.
*
* @param TAlign A type whose size is the desired memory alignment for object allocations.
* This should be the largest known alignment requirement for all objects
* that may be stored in the list.
*/
template<typename TBase, typename TAlign> class GrTRecorder : SkNoncopyable {
public:
class Iter;
class ReverseIter;
/**
* Create a recorder.
*
* @param initialSizeInBytes The amount of memory reserved by the recorder initially,
and after calls to reset().
*/
GrTRecorder(int initialSizeInBytes)
: fHeadBlock(MemBlock::Alloc(LengthOf(initialSizeInBytes), nullptr)),
fTailBlock(fHeadBlock),
fLastItem(nullptr) {}
~GrTRecorder() {
this->reset();
MemBlock::Free(fHeadBlock);
}
bool empty() { return !fLastItem; }
TBase& back() {
SkASSERT(!this->empty());
return *reinterpret_cast<TBase*>(fLastItem);
}
/**
* Removes and destroys the last block added to the recorder. It may not be called when the
* recorder is empty.
*/
void pop_back();
/**
* Destruct all items in the list and reset to empty.
*/
void reset();
/**
* Retrieve the extra data associated with an item that was allocated using
* GrNEW_APPEND_WITH_DATA_TO_RECORDER().
*
* @param item The item whose data to retrieve. The pointer must be of the same type
* that was allocated initally; it can't be a pointer to a base class.
*
* @return The item's associated data.
*/
template<typename TItem> static const void* GetDataForItem(const TItem* item) {
const TAlign* ptr = reinterpret_cast<const TAlign*>(item);
return &ptr[length_of<TItem>::kValue];
}
template<typename TItem> static void* GetDataForItem(TItem* item) {
TAlign* ptr = reinterpret_cast<TAlign*>(item);
return &ptr[length_of<TItem>::kValue];
}
private:
template<typename TItem> struct length_of {
enum { kValue = (sizeof(TItem) + sizeof(TAlign) - 1) / sizeof(TAlign) };
};
static int LengthOf(int bytes) { return (bytes + sizeof(TAlign) - 1) / sizeof(TAlign); }
struct Header {
int fTotalLength; // The length of an entry including header, item, and data in TAligns.
int fPrevLength; // Same but for the previous entry. Used for iterating backwards.
};
template<typename TItem> void* alloc_back(int dataLength);
struct MemBlock : SkNoncopyable {
/** Allocates a new block and appends it to prev if not nullptr. The length param is in units
of TAlign. */
static MemBlock* Alloc(int length, MemBlock* prev) {
MemBlock* block = reinterpret_cast<MemBlock*>(
sk_malloc_throw(sizeof(TAlign) * (length_of<MemBlock>::kValue + length)));
block->fLength = length;
block->fBack = 0;
block->fNext = nullptr;
block->fPrev = prev;
if (prev) {
SkASSERT(nullptr == prev->fNext);
prev->fNext = block;
}
return block;
}
// Frees from this block forward. Also adjusts prev block's next ptr.
static void Free(MemBlock* block) {
if (block && block->fPrev) {
SkASSERT(block->fPrev->fNext == block);
block->fPrev->fNext = nullptr;
}
while (block) {
MemBlock* next = block->fNext;
sk_free(block);
block = next;
}
}
TAlign& operator [](int i) {
return reinterpret_cast<TAlign*>(this)[length_of<MemBlock>::kValue + i];
}
int fLength; // Length in units of TAlign of the block.
int fBack; // Offset, in TAligns, to unused portion of the memory block.
MemBlock* fNext;
MemBlock* fPrev;
};
MemBlock* const fHeadBlock;
MemBlock* fTailBlock;
void* fLastItem; // really a ptr to TBase
template<typename TItem> friend struct GrTRecorderAllocWrapper;
template <typename UBase, typename UAlign, typename UItem>
friend void* operator new(size_t, GrTRecorder<UBase, UAlign>&,
const GrTRecorderAllocWrapper<UItem>&);
friend class Iter;
friend class ReverseIter;
};
////////////////////////////////////////////////////////////////////////////////
template<typename TBase, typename TAlign>
void GrTRecorder<TBase, TAlign>::pop_back() {
SkASSERT(fLastItem);
Header* header = reinterpret_cast<Header*>(
reinterpret_cast<TAlign*>(fLastItem) - length_of<Header>::kValue);
fTailBlock->fBack -= header->fTotalLength;
reinterpret_cast<TBase*>(fLastItem)->~TBase();
int lastItemLength = header->fPrevLength;
if (!header->fPrevLength) {
// We popped the first entry in the recorder.
SkASSERT(0 == fTailBlock->fBack);
fLastItem = nullptr;
return;
}
while (!fTailBlock->fBack) {
// We popped the last entry in a block that isn't the head block. Move back a block but
// don't free it since we'll probably grow into it shortly.
fTailBlock = fTailBlock->fPrev;
SkASSERT(fTailBlock);
}
fLastItem = &(*fTailBlock)[fTailBlock->fBack - lastItemLength + length_of<Header>::kValue];
}
template<typename TBase, typename TAlign>
template<typename TItem>
void* GrTRecorder<TBase, TAlign>::alloc_back(int dataLength) {
// Find the header of the previous entry and get its length. We need to store that in the new
// header for backwards iteration (pop_back()).
int prevLength = 0;
if (fLastItem) {
Header* lastHeader = reinterpret_cast<Header*>(
reinterpret_cast<TAlign*>(fLastItem) - length_of<Header>::kValue);
prevLength = lastHeader->fTotalLength;
}
const int totalLength = length_of<Header>::kValue + length_of<TItem>::kValue + dataLength;
// Check if there is room in the current block and if not walk to next (allocating if
// necessary). Note that pop_back() and reset() can leave the recorder in a state where it
// has preallocated blocks hanging off the tail that are currently unused.
while (fTailBlock->fBack + totalLength > fTailBlock->fLength) {
if (!fTailBlock->fNext) {
fTailBlock = MemBlock::Alloc(SkTMax(2 * fTailBlock->fLength, totalLength), fTailBlock);
} else {
fTailBlock = fTailBlock->fNext;
}
SkASSERT(0 == fTailBlock->fBack);
}
Header* header = reinterpret_cast<Header*>(&(*fTailBlock)[fTailBlock->fBack]);
void* rawPtr = &(*fTailBlock)[fTailBlock->fBack + length_of<Header>::kValue];
header->fTotalLength = totalLength;
header->fPrevLength = prevLength;
fLastItem = rawPtr;
fTailBlock->fBack += totalLength;
// FIXME: We currently require that the base and subclass share the same start address.
// This is not required by the C++ spec, and is likely to not be true in the case of
// multiple inheritance or a base class that doesn't have virtual methods (when the
// subclass does). It would be ideal to find a more robust solution that comes at no
// extra cost to performance or code generality.
SkDEBUGCODE(void* baseAddr = fLastItem;
void* subclassAddr = rawPtr);
SkASSERT(baseAddr == subclassAddr);
return rawPtr;
}
/**
* Iterates through a recorder from front to back. The initial state of the iterator is
* to not have the front item loaded yet; next() must be called first. Usage model:
*
* GrTRecorder<TBase, TAlign>::Iter iter(recorder);
* while (iter.next()) {
* iter->doSomething();
* }
*/
template<typename TBase, typename TAlign>
class GrTRecorder<TBase, TAlign>::Iter {
public:
Iter(GrTRecorder& recorder) : fBlock(recorder.fHeadBlock), fPosition(0), fItem(nullptr) {}
bool next() {
while (fPosition >= fBlock->fBack) {
SkASSERT(fPosition == fBlock->fBack);
if (!fBlock->fNext) {
return false;
}
fBlock = fBlock->fNext;
fPosition = 0;
}
Header* header = reinterpret_cast<Header*>(&(*fBlock)[fPosition]);
fItem = reinterpret_cast<TBase*>(&(*fBlock)[fPosition + length_of<Header>::kValue]);
fPosition += header->fTotalLength;
return true;
}
TBase* get() const {
SkASSERT(fItem);
return fItem;
}
TBase* operator->() const { return this->get(); }
private:
MemBlock* fBlock;
int fPosition;
TBase* fItem;
};
/**
* Iterates through a recorder in reverse, from back to front. This version mirrors "Iter",
* so the initial state is to have recorder.back() loaded already. (Note that this will
* assert if the recorder is empty.) Usage model:
*
* GrTRecorder<TBase, TAlign>::ReverseIter reverseIter(recorder);
* do {
* reverseIter->doSomething();
* } while (reverseIter.previous());
*/
template<typename TBase, typename TAlign>
class GrTRecorder<TBase, TAlign>::ReverseIter {
public:
ReverseIter(GrTRecorder& recorder)
: fBlock(recorder.fTailBlock),
fItem(&recorder.back()) {
Header* lastHeader = reinterpret_cast<Header*>(
reinterpret_cast<TAlign*>(fItem) - length_of<Header>::kValue);
fPosition = fBlock->fBack - lastHeader->fTotalLength;
}
bool previous() {
Header* header = reinterpret_cast<Header*>(&(*fBlock)[fPosition]);
while (0 == fPosition) {
if (!fBlock->fPrev) {
// We've reached the front of the recorder.
return false;
}
fBlock = fBlock->fPrev;
fPosition = fBlock->fBack;
}
fPosition -= header->fPrevLength;
SkASSERT(fPosition >= 0);
fItem = reinterpret_cast<TBase*>(&(*fBlock)[fPosition + length_of<Header>::kValue]);
return true;
}
TBase* get() const { return fItem; }
TBase* operator->() const { return this->get(); }
private:
MemBlock* fBlock;
int fPosition;
TBase* fItem;
};
template<typename TBase, typename TAlign>
void GrTRecorder<TBase, TAlign>::reset() {
Iter iter(*this);
while (iter.next()) {
iter->~TBase();
}
// Assume the next time this recorder fills up it will use approximately the same
// amount of space as last time. Leave enough space for up to ~50% growth; free
// everything else.
if (fTailBlock->fBack <= fTailBlock->fLength / 2) {
MemBlock::Free(fTailBlock->fNext);
} else if (fTailBlock->fNext) {
MemBlock::Free(fTailBlock->fNext->fNext);
fTailBlock->fNext->fNext = nullptr;
}
for (MemBlock* block = fHeadBlock; block; block = block->fNext) {
block->fBack = 0;
}
fTailBlock = fHeadBlock;
fLastItem = nullptr;
}
////////////////////////////////////////////////////////////////////////////////
template<typename TItem> struct GrTRecorderAllocWrapper {
GrTRecorderAllocWrapper() : fDataLength(0) {}
template <typename TBase, typename TAlign>
GrTRecorderAllocWrapper(const GrTRecorder<TBase, TAlign>&, int sizeOfData)
: fDataLength(GrTRecorder<TBase, TAlign>::LengthOf(sizeOfData)) {}
const int fDataLength;
};
template <typename TBase, typename TAlign, typename TItem>
void* operator new(size_t size, GrTRecorder<TBase, TAlign>& recorder,
const GrTRecorderAllocWrapper<TItem>& wrapper) {
SkASSERT(size == sizeof(TItem));
return recorder.template alloc_back<TItem>(wrapper.fDataLength);
}
template <typename TBase, typename TAlign, typename TItem>
void operator delete(void*, GrTRecorder<TBase, TAlign>&, const GrTRecorderAllocWrapper<TItem>&) {
// We only provide an operator delete to work around compiler warnings that can come
// up for an unmatched operator new when compiling with exceptions.
SK_ABORT("Invalid Operation");
}
#define GrNEW_APPEND_TO_RECORDER(recorder, type_name, args) \
(new (recorder, GrTRecorderAllocWrapper<type_name>()) type_name args)
#define GrNEW_APPEND_WITH_DATA_TO_RECORDER(recorder, type_name, args, size_of_data) \
(new (recorder, GrTRecorderAllocWrapper<type_name>(recorder, size_of_data)) type_name args)
#endif