1 /*
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
11 #include "SkTypes.h"
12
13 template<typename TBase, typename TAlign> class GrTRecorder;
14 template<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 */
45 template<typename TBase, typename TAlign> class GrTRecorder : SkNoncopyable {
46 public:
47 class Iter;
48 class ReverseIter;
49
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 */
GrTRecorder(int initialSizeInBytes)56 GrTRecorder(int initialSizeInBytes)
57 : fHeadBlock(MemBlock::Alloc(LengthOf(initialSizeInBytes), nullptr)),
58 fTailBlock(fHeadBlock),
59 fLastItem(nullptr) {}
60
~GrTRecorder()61 ~GrTRecorder() {
62 this->reset();
63 MemBlock::Free(fHeadBlock);
64 }
65
empty()66 bool empty() { return !fLastItem; }
67
back()68 TBase& back() {
69 SkASSERT(!this->empty());
70 return *reinterpret_cast<TBase*>(fLastItem);
71 }
72
73 /**
74 * 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 /**
80 * 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 */
GetDataForItem(const TItem * item)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 }
GetDataForItem(TItem * item)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
102 private:
103 template<typename TItem> struct length_of {
104 enum { kValue = (sizeof(TItem) + sizeof(TAlign) - 1) / sizeof(TAlign) };
105 };
LengthOf(int bytes)106 static int LengthOf(int bytes) { return (bytes + sizeof(TAlign) - 1) / sizeof(TAlign); }
107
108 struct Header {
109 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.
111 };
112 template<typename TItem> void* alloc_back(int dataLength);
113
114 struct MemBlock : SkNoncopyable {
115 /** Allocates a new block and appends it to prev if not nullptr. The length param is in units
116 of TAlign. */
AllocMemBlock117 static MemBlock* Alloc(int length, MemBlock* prev) {
118 MemBlock* block = reinterpret_cast<MemBlock*>(
119 sk_malloc_throw(sizeof(TAlign) * (length_of<MemBlock>::kValue + length)));
120 block->fLength = length;
121 block->fBack = 0;
122 block->fNext = nullptr;
123 block->fPrev = prev;
124 if (prev) {
125 SkASSERT(nullptr == prev->fNext);
126 prev->fNext = block;
127 }
128 return block;
129 }
130
131 // Frees from this block forward. Also adjusts prev block's next ptr.
FreeMemBlock132 static void Free(MemBlock* block) {
133 if (block && block->fPrev) {
134 SkASSERT(block->fPrev->fNext == block);
135 block->fPrev->fNext = nullptr;
136 }
137 while (block) {
138 MemBlock* next = block->fNext;
139 sk_free(block);
140 block = next;
141 }
142 }
143
144 TAlign& operator [](int i) {
145 return reinterpret_cast<TAlign*>(this)[length_of<MemBlock>::kValue + i];
146 }
147
148 int fLength; // Length in units of TAlign of the block.
149 int fBack; // Offset, in TAligns, to unused portion of the memory block.
150 MemBlock* fNext;
151 MemBlock* fPrev;
152 };
153 MemBlock* const fHeadBlock;
154 MemBlock* fTailBlock;
155
156 void* fLastItem; // really a ptr to TBase
157
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;
165 friend class ReverseIter;
166 };
167
168 ////////////////////////////////////////////////////////////////////////////////
169
170 template<typename TBase, typename TAlign>
pop_back()171 void 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;
176 reinterpret_cast<TBase*>(fLastItem)->~TBase();
177
178 int lastItemLength = header->fPrevLength;
179
180 if (!header->fPrevLength) {
181 // We popped the first entry in the recorder.
182 SkASSERT(0 == fTailBlock->fBack);
183 fLastItem = nullptr;
184 return;
185 }
186 while (!fTailBlock->fBack) {
187 // 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 }
192 fLastItem = &(*fTailBlock)[fTailBlock->fBack - lastItemLength + length_of<Header>::kValue];
193 }
194
195 template<typename TBase, typename TAlign>
196 template<typename TItem>
alloc_back(int dataLength)197 void* GrTRecorder<TBase, TAlign>::alloc_back(int dataLength) {
198 // 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
207 const int totalLength = length_of<Header>::kValue + length_of<TItem>::kValue + dataLength;
208
209 // 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.
212 while (fTailBlock->fBack + totalLength > fTailBlock->fLength) {
213 if (!fTailBlock->fNext) {
214 fTailBlock = MemBlock::Alloc(SkTMax(2 * fTailBlock->fLength, totalLength), fTailBlock);
215 } else {
216 fTailBlock = fTailBlock->fNext;
217 }
218 SkASSERT(0 == fTailBlock->fBack);
219 }
220
221 Header* header = reinterpret_cast<Header*>(&(*fTailBlock)[fTailBlock->fBack]);
222 void* rawPtr = &(*fTailBlock)[fTailBlock->fBack + length_of<Header>::kValue];
223
224 header->fTotalLength = totalLength;
225 header->fPrevLength = prevLength;
226 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
241 /**
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 */
250 template<typename TBase, typename TAlign>
251 class GrTRecorder<TBase, TAlign>::Iter {
252 public:
Iter(GrTRecorder & recorder)253 Iter(GrTRecorder& recorder) : fBlock(recorder.fHeadBlock), fPosition(0), fItem(nullptr) {}
254
next()255 bool next() {
256 while (fPosition >= fBlock->fBack) {
257 SkASSERT(fPosition == fBlock->fBack);
258 if (!fBlock->fNext) {
259 return false;
260 }
261 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
get()271 TBase* get() const {
272 SkASSERT(fItem);
273 return fItem;
274 }
275
276 TBase* operator->() const { return this->get(); }
277
278 private:
279 MemBlock* fBlock;
280 int fPosition;
281 TBase* fItem;
282 };
283
284 /**
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 */
294 template<typename TBase, typename TAlign>
295 class GrTRecorder<TBase, TAlign>::ReverseIter {
296 public:
ReverseIter(GrTRecorder & recorder)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
previous()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
get()324 TBase* get() const { return fItem; }
325 TBase* operator->() const { return this->get(); }
326
327 private:
328 MemBlock* fBlock;
329 int fPosition;
330 TBase* fItem;
331 };
332
333 template<typename TBase, typename TAlign>
reset()334 void GrTRecorder<TBase, TAlign>::reset() {
335 Iter iter(*this);
336 while (iter.next()) {
337 iter->~TBase();
338 }
339
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);
345 } else if (fTailBlock->fNext) {
346 MemBlock::Free(fTailBlock->fNext->fNext);
347 fTailBlock->fNext->fNext = nullptr;
348 }
349
350 for (MemBlock* block = fHeadBlock; block; block = block->fNext) {
351 block->fBack = 0;
352 }
353
354 fTailBlock = fHeadBlock;
355 fLastItem = nullptr;
356 }
357
358 ////////////////////////////////////////////////////////////////////////////////
359
360 template<typename TItem> struct GrTRecorderAllocWrapper {
GrTRecorderAllocWrapperGrTRecorderAllocWrapper361 GrTRecorderAllocWrapper() : fDataLength(0) {}
362
363 template <typename TBase, typename TAlign>
GrTRecorderAllocWrapperGrTRecorderAllocWrapper364 GrTRecorderAllocWrapper(const GrTRecorder<TBase, TAlign>&, int sizeOfData)
365 : fDataLength(GrTRecorder<TBase, TAlign>::LengthOf(sizeOfData)) {}
366
367 const int fDataLength;
368 };
369
370 template <typename TBase, typename TAlign, typename TItem>
new(size_t size,GrTRecorder<TBase,TAlign> & recorder,const GrTRecorderAllocWrapper<TItem> & wrapper)371 void* 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
377 template <typename TBase, typename TAlign, typename TItem>
delete(void *,GrTRecorder<TBase,TAlign> &,const GrTRecorderAllocWrapper<TItem> &)378 void 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.
381 SK_ABORT("Invalid Operation");
382 }
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
391