/* * 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" #include template class GrTRecorder; template 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 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(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 static const void* GetDataForItem(const TItem* item) { const TAlign* ptr = reinterpret_cast(item); return &ptr[length_of::kValue]; } template static void* GetDataForItem(TItem* item) { TAlign* ptr = reinterpret_cast(item); return &ptr[length_of::kValue]; } private: template 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 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( sk_malloc_throw(sizeof(TAlign) * (length_of::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(this)[length_of::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 friend struct GrTRecorderAllocWrapper; template friend void* operator new(size_t, GrTRecorder&, const GrTRecorderAllocWrapper&); friend class Iter; friend class ReverseIter; }; //////////////////////////////////////////////////////////////////////////////// template void GrTRecorder::pop_back() { SkASSERT(fLastItem); Header* header = reinterpret_cast( reinterpret_cast(fLastItem) - length_of
::kValue); fTailBlock->fBack -= header->fTotalLength; reinterpret_cast(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
::kValue]; } template template void* GrTRecorder::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( reinterpret_cast(fLastItem) - length_of
::kValue); prevLength = lastHeader->fTotalLength; } const int totalLength = length_of
::kValue + length_of::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(&(*fTailBlock)[fTailBlock->fBack]); void* rawPtr = &(*fTailBlock)[fTailBlock->fBack + length_of
::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::Iter iter(recorder); * while (iter.next()) { * iter->doSomething(); * } */ template class GrTRecorder::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(&(*fBlock)[fPosition]); fItem = reinterpret_cast(&(*fBlock)[fPosition + length_of
::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::ReverseIter reverseIter(recorder); * do { * reverseIter->doSomething(); * } while (reverseIter.previous()); */ template class GrTRecorder::ReverseIter { public: ReverseIter(GrTRecorder& recorder) : fBlock(recorder.fTailBlock), fItem(&recorder.back()) { Header* lastHeader = reinterpret_cast( reinterpret_cast(fItem) - length_of
::kValue); fPosition = fBlock->fBack - lastHeader->fTotalLength; } bool previous() { Header* header = reinterpret_cast(&(*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(&(*fBlock)[fPosition + length_of
::kValue]); return true; } TBase* get() const { return fItem; } TBase* operator->() const { return this->get(); } private: MemBlock* fBlock; int fPosition; TBase* fItem; }; template void GrTRecorder::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 struct GrTRecorderAllocWrapper { GrTRecorderAllocWrapper() : fDataLength(0) {} template GrTRecorderAllocWrapper(const GrTRecorder&, int sizeOfData) : fDataLength(GrTRecorder::LengthOf(sizeOfData)) {} const int fDataLength; }; template void* operator new(size_t size, GrTRecorder& recorder, const GrTRecorderAllocWrapper& wrapper) { SkASSERT(size == sizeof(TItem)); return recorder.template alloc_back(wrapper.fDataLength); } template void operator delete(void*, GrTRecorder&, const GrTRecorderAllocWrapper&) { // 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 args) #define GrNEW_APPEND_WITH_DATA_TO_RECORDER(recorder, type_name, args, size_of_data) \ (new (recorder, GrTRecorderAllocWrapper(recorder, size_of_data)) type_name args) #endif