blob: c42d9e975181f0ecf55d87d681641dde63b65d92 [file] [log] [blame]
cdalton6819df32014-10-15 13:43:48 -07001/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#ifndef GrTRecorder_DEFINED
9#define GrTRecorder_DEFINED
10
cdalton6819df32014-10-15 13:43:48 -070011#include "SkTypes.h"
12
13template<typename TBase, typename TAlign> class GrTRecorder;
14template<typename TItem> struct GrTRecorderAllocWrapper;
15
16/**
17 * Records a list of items with a common base type, optional associated data, and
18 * permanent memory addresses.
19 *
20 * This class preallocates its own chunks of memory for hosting objects, so new items can
21 * be created without excessive calls to malloc().
22 *
23 * To create a new item and append it to the back of the list, use the following macros:
24 *
25 * GrNEW_APPEND_TO_RECORDER(recorder, SubclassName, (args))
26 * GrNEW_APPEND_WITH_DATA_TO_RECORDER(recorder, SubclassName, (args), sizeOfData)
27 *
28 * Upon reset or delete, the items are destructed in the same order they were received,
29 * not reverse (stack) order.
30 *
31 * @param TBase Common base type of items in the list. If TBase is not a class with a
32 * virtual destructor, the client is responsible for invoking any necessary
33 * destructors.
34 *
35 * For now, any subclass used in the list must have the same start address
36 * as TBase (or in other words, the types must be convertible via
37 * reinterpret_cast<>). Classes with multiple inheritance (or any subclass
38 * on an obscure compiler) may not be compatible. This is runtime asserted
39 * in debug builds.
40 *
41 * @param TAlign A type whose size is the desired memory alignment for object allocations.
42 * This should be the largest known alignment requirement for all objects
43 * that may be stored in the list.
44 */
45template<typename TBase, typename TAlign> class GrTRecorder : SkNoncopyable {
46public:
47 class Iter;
cdalton72badbd2015-04-16 10:42:49 -070048 class ReverseIter;
cdalton6819df32014-10-15 13:43:48 -070049
50 /**
51 * Create a recorder.
52 *
53 * @param initialSizeInBytes The amount of memory reserved by the recorder initially,
54 and after calls to reset().
55 */
56 GrTRecorder(int initialSizeInBytes)
halcanary96fcdcc2015-08-27 07:41:13 -070057 : fHeadBlock(MemBlock::Alloc(LengthOf(initialSizeInBytes), nullptr)),
cdalton6819df32014-10-15 13:43:48 -070058 fTailBlock(fHeadBlock),
halcanary96fcdcc2015-08-27 07:41:13 -070059 fLastItem(nullptr) {}
cdalton6819df32014-10-15 13:43:48 -070060
61 ~GrTRecorder() {
62 this->reset();
63 MemBlock::Free(fHeadBlock);
64 }
65
66 bool empty() { return !fLastItem; }
67
68 TBase& back() {
69 SkASSERT(!this->empty());
bsalomon2aad5f12015-07-30 15:57:33 -070070 return *reinterpret_cast<TBase*>(fLastItem);
cdalton6819df32014-10-15 13:43:48 -070071 }
72
73 /**
bsalomon77d77f42014-11-21 14:38:06 -080074 * Removes and destroys the last block added to the recorder. It may not be called when the
75 * recorder is empty.
76 */
77 void pop_back();
78
79 /**
cdalton6819df32014-10-15 13:43:48 -070080 * Destruct all items in the list and reset to empty.
81 */
82 void reset();
83
84 /**
85 * Retrieve the extra data associated with an item that was allocated using
86 * GrNEW_APPEND_WITH_DATA_TO_RECORDER().
87 *
88 * @param item The item whose data to retrieve. The pointer must be of the same type
89 * that was allocated initally; it can't be a pointer to a base class.
90 *
91 * @return The item's associated data.
92 */
93 template<typename TItem> static const void* GetDataForItem(const TItem* item) {
94 const TAlign* ptr = reinterpret_cast<const TAlign*>(item);
95 return &ptr[length_of<TItem>::kValue];
96 }
97 template<typename TItem> static void* GetDataForItem(TItem* item) {
98 TAlign* ptr = reinterpret_cast<TAlign*>(item);
99 return &ptr[length_of<TItem>::kValue];
100 }
101
102private:
103 template<typename TItem> struct length_of {
104 enum { kValue = (sizeof(TItem) + sizeof(TAlign) - 1) / sizeof(TAlign) };
105 };
106 static int LengthOf(int bytes) { return (bytes + sizeof(TAlign) - 1) / sizeof(TAlign); }
107
108 struct Header {
bsalomon77d77f42014-11-21 14:38:06 -0800109 int fTotalLength; // The length of an entry including header, item, and data in TAligns.
110 int fPrevLength; // Same but for the previous entry. Used for iterating backwards.
cdalton6819df32014-10-15 13:43:48 -0700111 };
bsalomon2aad5f12015-07-30 15:57:33 -0700112 template<typename TItem> void* alloc_back(int dataLength);
cdalton6819df32014-10-15 13:43:48 -0700113
114 struct MemBlock : SkNoncopyable {
halcanary96fcdcc2015-08-27 07:41:13 -0700115 /** Allocates a new block and appends it to prev if not nullptr. The length param is in units
bsalomon77d77f42014-11-21 14:38:06 -0800116 of TAlign. */
117 static MemBlock* Alloc(int length, MemBlock* prev) {
cdalton6819df32014-10-15 13:43:48 -0700118 MemBlock* block = reinterpret_cast<MemBlock*>(
119 sk_malloc_throw(sizeof(TAlign) * (length_of<MemBlock>::kValue + length)));
120 block->fLength = length;
121 block->fBack = 0;
halcanary96fcdcc2015-08-27 07:41:13 -0700122 block->fNext = nullptr;
bsalomon77d77f42014-11-21 14:38:06 -0800123 block->fPrev = prev;
124 if (prev) {
halcanary96fcdcc2015-08-27 07:41:13 -0700125 SkASSERT(nullptr == prev->fNext);
bsalomon77d77f42014-11-21 14:38:06 -0800126 prev->fNext = block;
127 }
cdalton6819df32014-10-15 13:43:48 -0700128 return block;
129 }
130
bsalomon77d77f42014-11-21 14:38:06 -0800131 // Frees from this block forward. Also adjusts prev block's next ptr.
cdalton6819df32014-10-15 13:43:48 -0700132 static void Free(MemBlock* block) {
bsalomon77d77f42014-11-21 14:38:06 -0800133 if (block && block->fPrev) {
134 SkASSERT(block->fPrev->fNext == block);
halcanary96fcdcc2015-08-27 07:41:13 -0700135 block->fPrev->fNext = nullptr;
cdalton6819df32014-10-15 13:43:48 -0700136 }
bsalomon77d77f42014-11-21 14:38:06 -0800137 while (block) {
138 MemBlock* next = block->fNext;
139 sk_free(block);
140 block = next;
141 }
cdalton6819df32014-10-15 13:43:48 -0700142 }
143
144 TAlign& operator [](int i) {
145 return reinterpret_cast<TAlign*>(this)[length_of<MemBlock>::kValue + i];
146 }
147
bsalomon77d77f42014-11-21 14:38:06 -0800148 int fLength; // Length in units of TAlign of the block.
149 int fBack; // Offset, in TAligns, to unused portion of the memory block.
cdalton6819df32014-10-15 13:43:48 -0700150 MemBlock* fNext;
bsalomon77d77f42014-11-21 14:38:06 -0800151 MemBlock* fPrev;
cdalton6819df32014-10-15 13:43:48 -0700152 };
153 MemBlock* const fHeadBlock;
154 MemBlock* fTailBlock;
155
bsalomon2aad5f12015-07-30 15:57:33 -0700156 void* fLastItem; // really a ptr to TBase
cdalton6819df32014-10-15 13:43:48 -0700157
158 template<typename TItem> friend struct GrTRecorderAllocWrapper;
159
160 template <typename UBase, typename UAlign, typename UItem>
161 friend void* operator new(size_t, GrTRecorder<UBase, UAlign>&,
162 const GrTRecorderAllocWrapper<UItem>&);
163
164 friend class Iter;
cdalton72badbd2015-04-16 10:42:49 -0700165 friend class ReverseIter;
cdalton6819df32014-10-15 13:43:48 -0700166};
167
168////////////////////////////////////////////////////////////////////////////////
169
170template<typename TBase, typename TAlign>
bsalomon77d77f42014-11-21 14:38:06 -0800171void GrTRecorder<TBase, TAlign>::pop_back() {
172 SkASSERT(fLastItem);
173 Header* header = reinterpret_cast<Header*>(
174 reinterpret_cast<TAlign*>(fLastItem) - length_of<Header>::kValue);
175 fTailBlock->fBack -= header->fTotalLength;
bsalomon2aad5f12015-07-30 15:57:33 -0700176 reinterpret_cast<TBase*>(fLastItem)->~TBase();
bsalomon77d77f42014-11-21 14:38:06 -0800177
178 int lastItemLength = header->fPrevLength;
179
180 if (!header->fPrevLength) {
181 // We popped the first entry in the recorder.
182 SkASSERT(0 == fTailBlock->fBack);
halcanary96fcdcc2015-08-27 07:41:13 -0700183 fLastItem = nullptr;
bsalomon77d77f42014-11-21 14:38:06 -0800184 return;
185 }
cdalton72badbd2015-04-16 10:42:49 -0700186 while (!fTailBlock->fBack) {
bsalomon77d77f42014-11-21 14:38:06 -0800187 // We popped the last entry in a block that isn't the head block. Move back a block but
188 // don't free it since we'll probably grow into it shortly.
189 fTailBlock = fTailBlock->fPrev;
190 SkASSERT(fTailBlock);
191 }
bsalomon2aad5f12015-07-30 15:57:33 -0700192 fLastItem = &(*fTailBlock)[fTailBlock->fBack - lastItemLength + length_of<Header>::kValue];
bsalomon77d77f42014-11-21 14:38:06 -0800193}
194
195template<typename TBase, typename TAlign>
cdalton6819df32014-10-15 13:43:48 -0700196template<typename TItem>
bsalomon2aad5f12015-07-30 15:57:33 -0700197void* GrTRecorder<TBase, TAlign>::alloc_back(int dataLength) {
bsalomon77d77f42014-11-21 14:38:06 -0800198 // Find the header of the previous entry and get its length. We need to store that in the new
199 // header for backwards iteration (pop_back()).
200 int prevLength = 0;
201 if (fLastItem) {
202 Header* lastHeader = reinterpret_cast<Header*>(
203 reinterpret_cast<TAlign*>(fLastItem) - length_of<Header>::kValue);
204 prevLength = lastHeader->fTotalLength;
205 }
206
cdalton6819df32014-10-15 13:43:48 -0700207 const int totalLength = length_of<Header>::kValue + length_of<TItem>::kValue + dataLength;
208
bsalomon77d77f42014-11-21 14:38:06 -0800209 // Check if there is room in the current block and if not walk to next (allocating if
210 // necessary). Note that pop_back() and reset() can leave the recorder in a state where it
211 // has preallocated blocks hanging off the tail that are currently unused.
cdaltonc4650ee2014-11-07 12:51:18 -0800212 while (fTailBlock->fBack + totalLength > fTailBlock->fLength) {
213 if (!fTailBlock->fNext) {
bsalomon77d77f42014-11-21 14:38:06 -0800214 fTailBlock = MemBlock::Alloc(SkTMax(2 * fTailBlock->fLength, totalLength), fTailBlock);
215 } else {
216 fTailBlock = fTailBlock->fNext;
cdaltonc4650ee2014-11-07 12:51:18 -0800217 }
cdaltonc4650ee2014-11-07 12:51:18 -0800218 SkASSERT(0 == fTailBlock->fBack);
cdalton6819df32014-10-15 13:43:48 -0700219 }
220
221 Header* header = reinterpret_cast<Header*>(&(*fTailBlock)[fTailBlock->fBack]);
bsalomon2aad5f12015-07-30 15:57:33 -0700222 void* rawPtr = &(*fTailBlock)[fTailBlock->fBack + length_of<Header>::kValue];
cdalton6819df32014-10-15 13:43:48 -0700223
224 header->fTotalLength = totalLength;
bsalomon77d77f42014-11-21 14:38:06 -0800225 header->fPrevLength = prevLength;
cdalton6819df32014-10-15 13:43:48 -0700226 fLastItem = rawPtr;
227 fTailBlock->fBack += totalLength;
228
229 // FIXME: We currently require that the base and subclass share the same start address.
230 // This is not required by the C++ spec, and is likely to not be true in the case of
231 // multiple inheritance or a base class that doesn't have virtual methods (when the
232 // subclass does). It would be ideal to find a more robust solution that comes at no
233 // extra cost to performance or code generality.
234 SkDEBUGCODE(void* baseAddr = fLastItem;
235 void* subclassAddr = rawPtr);
236 SkASSERT(baseAddr == subclassAddr);
237
238 return rawPtr;
239}
240
cdalton72badbd2015-04-16 10:42:49 -0700241/**
242 * Iterates through a recorder from front to back. The initial state of the iterator is
243 * to not have the front item loaded yet; next() must be called first. Usage model:
244 *
245 * GrTRecorder<TBase, TAlign>::Iter iter(recorder);
246 * while (iter.next()) {
247 * iter->doSomething();
248 * }
249 */
cdalton6819df32014-10-15 13:43:48 -0700250template<typename TBase, typename TAlign>
251class GrTRecorder<TBase, TAlign>::Iter {
252public:
halcanary96fcdcc2015-08-27 07:41:13 -0700253 Iter(GrTRecorder& recorder) : fBlock(recorder.fHeadBlock), fPosition(0), fItem(nullptr) {}
cdalton6819df32014-10-15 13:43:48 -0700254
255 bool next() {
cdaltonc4650ee2014-11-07 12:51:18 -0800256 while (fPosition >= fBlock->fBack) {
cdalton6819df32014-10-15 13:43:48 -0700257 SkASSERT(fPosition == fBlock->fBack);
258 if (!fBlock->fNext) {
259 return false;
260 }
cdalton6819df32014-10-15 13:43:48 -0700261 fBlock = fBlock->fNext;
262 fPosition = 0;
263 }
264
265 Header* header = reinterpret_cast<Header*>(&(*fBlock)[fPosition]);
266 fItem = reinterpret_cast<TBase*>(&(*fBlock)[fPosition + length_of<Header>::kValue]);
267 fPosition += header->fTotalLength;
268 return true;
269 }
270
271 TBase* get() const {
272 SkASSERT(fItem);
273 return fItem;
274 }
275
276 TBase* operator->() const { return this->get(); }
277
278private:
279 MemBlock* fBlock;
280 int fPosition;
281 TBase* fItem;
282};
283
cdalton72badbd2015-04-16 10:42:49 -0700284/**
285 * Iterates through a recorder in reverse, from back to front. This version mirrors "Iter",
286 * so the initial state is to have recorder.back() loaded already. (Note that this will
287 * assert if the recorder is empty.) Usage model:
288 *
289 * GrTRecorder<TBase, TAlign>::ReverseIter reverseIter(recorder);
290 * do {
291 * reverseIter->doSomething();
292 * } while (reverseIter.previous());
293 */
294template<typename TBase, typename TAlign>
295class GrTRecorder<TBase, TAlign>::ReverseIter {
296public:
297 ReverseIter(GrTRecorder& recorder)
298 : fBlock(recorder.fTailBlock),
299 fItem(&recorder.back()) {
300 Header* lastHeader = reinterpret_cast<Header*>(
301 reinterpret_cast<TAlign*>(fItem) - length_of<Header>::kValue);
302 fPosition = fBlock->fBack - lastHeader->fTotalLength;
303 }
304
305 bool previous() {
306 Header* header = reinterpret_cast<Header*>(&(*fBlock)[fPosition]);
307
308 while (0 == fPosition) {
309 if (!fBlock->fPrev) {
310 // We've reached the front of the recorder.
311 return false;
312 }
313 fBlock = fBlock->fPrev;
314 fPosition = fBlock->fBack;
315 }
316
317 fPosition -= header->fPrevLength;
318 SkASSERT(fPosition >= 0);
319
320 fItem = reinterpret_cast<TBase*>(&(*fBlock)[fPosition + length_of<Header>::kValue]);
321 return true;
322 }
323
324 TBase* get() const { return fItem; }
325 TBase* operator->() const { return this->get(); }
326
327private:
328 MemBlock* fBlock;
329 int fPosition;
330 TBase* fItem;
331};
332
cdalton6819df32014-10-15 13:43:48 -0700333template<typename TBase, typename TAlign>
334void GrTRecorder<TBase, TAlign>::reset() {
335 Iter iter(*this);
336 while (iter.next()) {
337 iter->~TBase();
338 }
cdaltonc4650ee2014-11-07 12:51:18 -0800339
340 // Assume the next time this recorder fills up it will use approximately the same
341 // amount of space as last time. Leave enough space for up to ~50% growth; free
342 // everything else.
343 if (fTailBlock->fBack <= fTailBlock->fLength / 2) {
344 MemBlock::Free(fTailBlock->fNext);
cdaltonc4650ee2014-11-07 12:51:18 -0800345 } else if (fTailBlock->fNext) {
346 MemBlock::Free(fTailBlock->fNext->fNext);
halcanary96fcdcc2015-08-27 07:41:13 -0700347 fTailBlock->fNext->fNext = nullptr;
cdaltonc4650ee2014-11-07 12:51:18 -0800348 }
349
350 for (MemBlock* block = fHeadBlock; block; block = block->fNext) {
351 block->fBack = 0;
352 }
353
cdalton6819df32014-10-15 13:43:48 -0700354 fTailBlock = fHeadBlock;
halcanary96fcdcc2015-08-27 07:41:13 -0700355 fLastItem = nullptr;
cdalton6819df32014-10-15 13:43:48 -0700356}
357
358////////////////////////////////////////////////////////////////////////////////
359
360template<typename TItem> struct GrTRecorderAllocWrapper {
361 GrTRecorderAllocWrapper() : fDataLength(0) {}
362
363 template <typename TBase, typename TAlign>
364 GrTRecorderAllocWrapper(const GrTRecorder<TBase, TAlign>&, int sizeOfData)
365 : fDataLength(GrTRecorder<TBase, TAlign>::LengthOf(sizeOfData)) {}
366
367 const int fDataLength;
368};
369
370template <typename TBase, typename TAlign, typename TItem>
371void* operator new(size_t size, GrTRecorder<TBase, TAlign>& recorder,
372 const GrTRecorderAllocWrapper<TItem>& wrapper) {
373 SkASSERT(size == sizeof(TItem));
374 return recorder.template alloc_back<TItem>(wrapper.fDataLength);
375}
376
377template <typename TBase, typename TAlign, typename TItem>
378void operator delete(void*, GrTRecorder<TBase, TAlign>&, const GrTRecorderAllocWrapper<TItem>&) {
379 // We only provide an operator delete to work around compiler warnings that can come
380 // up for an unmatched operator new when compiling with exceptions.
djsollenf2b340f2016-01-29 08:51:04 -0800381 SK_ABORT("Invalid Operation");
cdalton6819df32014-10-15 13:43:48 -0700382}
383
384#define GrNEW_APPEND_TO_RECORDER(recorder, type_name, args) \
385 (new (recorder, GrTRecorderAllocWrapper<type_name>()) type_name args)
386
387#define GrNEW_APPEND_WITH_DATA_TO_RECORDER(recorder, type_name, args, size_of_data) \
388 (new (recorder, GrTRecorderAllocWrapper<type_name>(recorder, size_of_data)) type_name args)
389
390#endif