Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkTBlockList.h
Go to the documentation of this file.
1/*
2 * Copyright 2010 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 SkTBlockList_DEFINED
9#define SkTBlockList_DEFINED
10
15
16#include <algorithm>
17#include <cstring>
18#include <type_traits>
19#include <utility>
20
21// Forward declarations for the iterators used by SkTBlockList
24template<typename T, typename B> using ItemFn = T (*)(B*, int);
25template <typename T, bool Forward, bool Const, IndexFn Start, IndexFn End, NextFn Next,
26 ItemFn<T, typename std::conditional<Const, const SkBlockAllocator::Block,
29
30/**
31 * SkTBlockList manages dynamic storage for instances of T, reserving fixed blocks such that
32 * allocation is amortized across every N instances. In this way it is a hybrid of an array-based
33 * vector and a linked-list. T can be any type and non-trivial destructors are automatically
34 * invoked when the SkTBlockList is destructed. The addresses of instances are guaranteed
35 * not to move except when a list is concatenated to another.
36 *
37 * The collection supports storing a templated number of elements inline before heap-allocated
38 * blocks are made to hold additional instances. By default, the heap blocks are sized to hold the
39 * same number of items as the inline block. A common pattern is to have the inline size hold only
40 * a small number of items for the common case and then allocate larger blocks when needed.
41 *
42 * If the size of a collection is N, and its block size is B, the complexity of the common
43 * operations are:
44 * - push_back()/emplace_back(): O(1), with malloc O(B)
45 * - pop_back(): O(1), with free O(B)
46 * - front()/back(): O(1)
47 * - reset(): O(N) for non-trivial types, O(N/B) for trivial types
48 * - concat(): O(B)
49 * - random access: O(N/B)
50 * - iteration: O(1) at each step
51 *
52 * These characteristics make it well suited for allocating items in a LIFO ordering, or otherwise
53 * acting as a stack, or simply using it as a typed allocator.
54 */
55template <typename T, int StartingItems = 1>
57public:
58 /**
59 * Create an list that defaults to using StartingItems as heap increment and the
60 * kFixed growth policy (e.g. all allocations will match StartingItems).
61 */
63
64 /**
65 * Create an list that defaults to using StartingItems as the heap increment, but with
66 * the defined growth policy.
67 */
69 : SkTBlockList(StartingItems, policy) {}
70
71 /**
72 * Create an list.
73 *
74 * @param itemsPerBlock the number of items to allocate at once
75 * @param policy the growth policy for subsequent blocks of items
76 */
77 explicit SkTBlockList(int itemsPerBlock,
79 SkBlockAllocator::GrowthPolicy::kFixed)
80 : fAllocator(policy,
81 SkBlockAllocator::BlockOverhead<alignof(T)>() + sizeof(T)*itemsPerBlock) {}
82
83 ~SkTBlockList() { this->reset(); }
84
85 /**
86 * Adds an item and returns it.
87 *
88 * @return the added item.
89 */
91 return *new (this->pushItem()) T;
92 }
93 T& push_back(const T& t) {
94 return *new (this->pushItem()) T(t);
95 }
96 T& push_back(T&& t) {
97 return *new (this->pushItem()) T(std::move(t));
98 }
99
100 template <typename... Args>
101 T& emplace_back(Args&&... args) {
102 return *new (this->pushItem()) T(std::forward<Args>(args)...);
103 }
104
105 /**
106 * Move all items from 'other' to the end of this collection. When this returns, 'other' will
107 * be empty. Items in 'other' may be moved as part of compacting the pre-allocated start of
108 * 'other' into this list (using T's move constructor or memcpy if T is trivially copyable), but
109 * this is O(StartingItems) and not O(N). All other items are concatenated in O(1).
110 */
111 template <int SI>
113
114 /**
115 * Allocate, if needed, space to hold N more Ts before another malloc will occur.
116 */
117 void reserve(int n) {
118 int avail = fAllocator->currentBlock()->template avail<alignof(T)>() / sizeof(T);
119 if (n > avail) {
120 int reserved = n - avail;
121 // Don't consider existing bytes since we've already determined how to split the N items
122 fAllocator->template reserve<alignof(T)>(
124 }
125 }
126
127 /**
128 * Remove the last item, only call if count() != 0
129 */
130 void pop_back() {
131 SkASSERT(this->count() > 0);
132
133 SkBlockAllocator::Block* block = fAllocator->currentBlock();
134
135 // Run dtor for the popped item
136 int releaseIndex = Last(block);
137 GetItem(block, releaseIndex).~T();
138
139 if (releaseIndex == First(block)) {
140 fAllocator->releaseBlock(block);
141 } else {
142 // Since this always follows LIFO, the block should always be able to release the memory
143 SkAssertResult(block->release(releaseIndex, releaseIndex + sizeof(T)));
144 block->setMetadata(Decrement(block, releaseIndex));
145 }
146
147 fAllocator->setMetadata(fAllocator->metadata() - 1);
148 }
149
150 /**
151 * Removes all added items.
152 */
153 void reset() {
154 // Invoke destructors in reverse order if not trivially destructible
155 if constexpr (!std::is_trivially_destructible<T>::value) {
156 for (T& t : this->ritems()) {
157 t.~T();
158 }
159 }
160
161 fAllocator->reset();
162 }
163
164 /**
165 * Returns the item count.
166 */
167 int count() const {
168#ifdef SK_DEBUG
169 // Confirm total count matches sum of block counts
170 int count = 0;
171 for (const auto* b :fAllocator->blocks()) {
172 if (b->metadata() == 0) {
173 continue; // skip empty
174 }
175 count += (sizeof(T) + Last(b) - First(b)) / sizeof(T);
176 }
177 SkASSERT(count == fAllocator->metadata());
178#endif
179 return fAllocator->metadata();
180 }
181
182 /**
183 * Is the count 0?
184 */
185 bool empty() const { return this->count() == 0; }
186
187 /**
188 * Access first item, only call if count() != 0
189 */
190 T& front() {
191 // This assumes that the head block actually have room to store the first item.
192 static_assert(StartingItems >= 1);
193 SkASSERT(this->count() > 0 && fAllocator->headBlock()->metadata() > 0);
194 return GetItem(fAllocator->headBlock(), First(fAllocator->headBlock()));
195 }
196 const T& front() const {
197 SkASSERT(this->count() > 0 && fAllocator->headBlock()->metadata() > 0);
198 return GetItem(fAllocator->headBlock(), First(fAllocator->headBlock()));
199 }
200
201 /**
202 * Access last item, only call if count() != 0
203 */
204 T& back() {
205 SkASSERT(this->count() > 0 && fAllocator->currentBlock()->metadata() > 0);
206 return GetItem(fAllocator->currentBlock(), Last(fAllocator->currentBlock()));
207 }
208 const T& back() const {
209 SkASSERT(this->count() > 0 && fAllocator->currentBlock()->metadata() > 0);
210 return GetItem(fAllocator->currentBlock(), Last(fAllocator->currentBlock()));
211 }
212
213 /**
214 * Access item by index. Not an operator[] since it should not be considered constant time.
215 * Use for-range loops by calling items() or ritems() instead to access all added items in order
216 */
217 T& item(int i) {
218 SkASSERT(i >= 0 && i < this->count());
219
220 // Iterate over blocks until we find the one that contains i.
221 for (auto* b : fAllocator->blocks()) {
222 if (b->metadata() == 0) {
223 continue; // skip empty
224 }
225
226 int start = First(b);
227 int end = Last(b) + sizeof(T); // exclusive
228 int index = start + i * sizeof(T);
229 if (index < end) {
230 return GetItem(b, index);
231 } else {
232 i -= (end - start) / sizeof(T);
233 }
234 }
236 }
237 const T& item(int i) const {
238 return const_cast<SkTBlockList*>(this)->item(i);
239 }
240
241private:
242 // Let other SkTBlockLists have access (only ever used when T and S are the same but you
243 // cannot have partial specializations declared as a friend...)
244 template<typename S, int N> friend class SkTBlockList;
245 friend class TBlockListTestAccess; // for fAllocator
246
247 inline static constexpr size_t StartingSize =
248 SkBlockAllocator::Overhead<alignof(T)>() + StartingItems * sizeof(T);
249
250 static T& GetItem(SkBlockAllocator::Block* block, int index) {
251 return *static_cast<T*>(block->ptr(index));
252 }
253 static const T& GetItem(const SkBlockAllocator::Block* block, int index) {
254 return *static_cast<const T*>(block->ptr(index));
255 }
256 static int First(const SkBlockAllocator::Block* b) {
257 return b->firstAlignedOffset<alignof(T)>();
258 }
259 static int Last(const SkBlockAllocator::Block* b) {
260 return b->metadata();
261 }
262 static int Increment(const SkBlockAllocator::Block* b, int index) {
263 return index + sizeof(T);
264 }
265 static int Decrement(const SkBlockAllocator::Block* b, int index) {
266 return index - sizeof(T);
267 }
268
269 void* pushItem() {
270 // 'template' required because fAllocator is a template, calling a template member
271 auto br = fAllocator->template allocate<alignof(T)>(sizeof(T));
272 SkASSERT(br.fStart == br.fAlignedOffset ||
273 br.fAlignedOffset == First(fAllocator->currentBlock()));
274 br.fBlock->setMetadata(br.fAlignedOffset);
275 fAllocator->setMetadata(fAllocator->metadata() + 1);
276 return br.fBlock->ptr(br.fAlignedOffset);
277 }
278
279 // N represents the number of items, whereas SkSBlockAllocator takes total bytes, so must
280 // account for the block allocator's size too.
281 //
282 // This class uses the SkBlockAllocator's metadata to track total count of items, and per-block
283 // metadata to track the index of the last allocated item within each block.
285
286public:
291
292 /**
293 * Iterate over all items in allocation order (oldest to newest) using a for-range loop:
294 *
295 * for (auto&& T : this->items()) {}
296 */
297 Iter items() { return Iter(fAllocator.allocator()); }
298 CIter items() const { return CIter(fAllocator.allocator()); }
299
300 // Iterate from newest to oldest using a for-range loop.
301 RIter ritems() { return RIter(fAllocator.allocator()); }
302 CRIter ritems() const { return CRIter(fAllocator.allocator()); }
303};
304
305template <typename T, int SI1>
306template <int SI2>
308 // Optimize the common case where the list to append only has a single item
309 if (other.empty()) {
310 return;
311 } else if (other.count() == 1) {
312 this->push_back(other.back());
313 other.pop_back();
314 return;
315 }
316
317 // Manually move all items in other's head block into this list; all heap blocks from 'other'
318 // will be appended to the block linked list (no per-item moves needed then).
319 int headItemCount = 0;
320 SkBlockAllocator::Block* headBlock = other.fAllocator->headBlock();
321 SkDEBUGCODE(int oldCount = this->count();)
322 if (headBlock->metadata() > 0) {
323 int headStart = First(headBlock);
324 int headEnd = Last(headBlock) + sizeof(T); // exclusive
325 headItemCount = (headEnd - headStart) / sizeof(T);
326 int avail = fAllocator->currentBlock()->template avail<alignof(T)>() / sizeof(T);
327 if (headItemCount > avail) {
328 // Make sure there is extra room for the items beyond what's already avail. Use the
329 // kIgnoreGrowthPolicy_Flag to make this reservation as tight as possible since
330 // 'other's heap blocks will be appended after it and any extra space is wasted.
331 fAllocator->template reserve<alignof(T)>((headItemCount - avail) * sizeof(T),
334 }
335
336 if constexpr (std::is_trivially_copy_constructible<T>::value) {
337 // memcpy all items at once (or twice between current and reserved space).
338 SkASSERT(std::is_trivially_destructible<T>::value);
339 auto copy = [](SkBlockAllocator::Block* src, int start, SkBlockAllocator* dst, int n) {
340 auto target = dst->template allocate<alignof(T)>(n * sizeof(T));
341 memcpy(target.fBlock->ptr(target.fAlignedOffset), src->ptr(start), n * sizeof(T));
342 target.fBlock->setMetadata(target.fAlignedOffset + (n - 1) * sizeof(T));
343 };
344
345 if (avail > 0) {
346 // Copy 0 to avail items into existing tail block
347 copy(headBlock, headStart, fAllocator.allocator(), std::min(headItemCount, avail));
348 }
349 if (headItemCount > avail) {
350 // Copy (head count - avail) into the extra reserved space
351 copy(headBlock, headStart + avail * sizeof(T),
352 fAllocator.allocator(), headItemCount - avail);
353 }
354 fAllocator->setMetadata(fAllocator->metadata() + headItemCount);
355 } else {
356 // Move every item over one at a time
357 for (int i = headStart; i < headEnd; i += sizeof(T)) {
358 T& toMove = GetItem(headBlock, i);
359 this->push_back(std::move(toMove));
360 // Anything of interest should have been moved, but run this since T isn't
361 // a trusted type.
362 toMove.~T(); // NOLINT(bugprone-use-after-move): calling dtor always allowed
363 }
364 }
365
366 other.fAllocator->releaseBlock(headBlock);
367 }
368
369 // other's head block must have been fully copied since it cannot be stolen
370 SkASSERT(other.fAllocator->headBlock()->metadata() == 0 &&
371 fAllocator->metadata() == oldCount + headItemCount);
372 fAllocator->stealHeapBlocks(other.fAllocator.allocator());
373 fAllocator->setMetadata(fAllocator->metadata() +
374 (other.fAllocator->metadata() - headItemCount));
375 other.fAllocator->setMetadata(0);
376}
377
378/**
379 * BlockIndexIterator provides a reusable iterator template for collections built on top of a
380 * SkBlockAllocator, where each item is of the same type, and the index to an item can be iterated
381 * over in a known manner. It supports const and non-const, and forward and reverse, assuming it's
382 * provided with proper functions for starting, ending, and advancing.
383 */
384template <typename T, // The element type (including any modifiers)
385 bool Forward, // Are indices within a block increasing or decreasing with iteration?
386 bool Const, // Whether or not T is const
387 IndexFn Start, // Returns the index of the first valid item in a block
388 IndexFn End, // Returns the index of the last valid item (so it is inclusive)
389 NextFn Next, // Returns the next index given the current index
390 ItemFn<T, typename std::conditional<Const, const SkBlockAllocator::Block,
393 using BlockIter = typename SkBlockAllocator::BlockIter<Forward, Const>;
394public:
395 BlockIndexIterator(BlockIter iter) : fBlockIter(iter) {}
396
397 class Item {
398 public:
399 bool operator!=(const Item& other) const {
400 return other.fBlock != fBlock || (SkToBool(*fBlock) && other.fIndex != fIndex);
401 }
402
403 T operator*() const {
404 SkASSERT(*fBlock);
405 return Resolve(*fBlock, fIndex);
406 }
407
409 const auto* block = *fBlock;
410 SkASSERT(block && block->metadata() > 0);
411 SkASSERT((Forward && Next(block, fIndex) > fIndex) ||
412 (!Forward && Next(block, fIndex) < fIndex));
413 fIndex = Next(block, fIndex);
414 if ((Forward && fIndex > fEndIndex) || (!Forward && fIndex < fEndIndex)) {
415 ++fBlock;
416 this->setIndices();
417 }
418 return *this;
419 }
420
421 private:
422 friend BlockIndexIterator;
423 using BlockItem = typename BlockIter::Item;
424
425 Item(BlockItem block) : fBlock(block) {
426 this->setIndices();
427 }
428
429 void setIndices() {
430 // Skip empty blocks
431 while(*fBlock && (*fBlock)->metadata() == 0) {
432 ++fBlock;
433 }
434 if (*fBlock) {
435 fIndex = Start(*fBlock);
436 fEndIndex = End(*fBlock);
437 } else {
438 fIndex = 0;
439 fEndIndex = 0;
440 }
441
442 SkASSERT((Forward && fIndex <= fEndIndex) || (!Forward && fIndex >= fEndIndex));
443 }
444
445 BlockItem fBlock;
446 int fIndex;
447 int fEndIndex;
448 };
449
450 Item begin() const { return Item(fBlockIter.begin()); }
451 Item end() const { return Item(fBlockIter.end()); }
452
453private:
454 BlockIter fBlockIter;
455};
456
457#endif
int count
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkUNREACHABLE
Definition SkAssert.h:135
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkDEBUGCODE(...)
Definition SkDebug.h:23
T(*)(B *, int) ItemFn
int(*)(const SkBlockAllocator::Block *, int) NextFn
int(*)(const SkBlockAllocator::Block *) IndexFn
static constexpr bool SkToBool(const T &x)
Definition SkTo.h:35
Type::kYUV Type::kRGBA() int(0.7 *637)
bool operator!=(const Item &other) const
Item begin() const
BlockIndexIterator(BlockIter iter)
bool release(int start, int end)
void * ptr(int offset)
void setMetadata(int value)
void setMetadata(int value)
void releaseBlock(Block *block)
const Block * headBlock() const
BlockIter< true, false > blocks()
const Block * currentBlock() const
SkBlockAllocator * allocator()
CIter items() const
const T & item(int i) const
const T & back() const
SkTBlockList(int itemsPerBlock, SkBlockAllocator::GrowthPolicy policy=SkBlockAllocator::GrowthPolicy::kFixed)
T & item(int i)
CRIter ritems() const
BlockIndexIterator< T &, true, false, &First, &Last, &Increment, &GetItem > Iter
const T & front() const
int count() const
BlockIndexIterator< const T &, false, true, &Last, &First, &Decrement, &GetItem > CRIter
T & emplace_back(Args &&... args)
BlockIndexIterator< const T &, true, true, &First, &Last, &Increment, &GetItem > CIter
SkTBlockList(SkBlockAllocator::GrowthPolicy policy)
T & push_back(T &&t)
bool empty() const
void concat(SkTBlockList< T, SI > &&other)
void reserve(int n)
BlockIndexIterator< T &, false, false, &Last, &First, &Decrement, &GetItem > RIter
T & push_back(const T &t)
static bool b
glong glong end
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint32_t * target
Definition copy.py:1
#define T