blob: 9c650e732284be0850a69828e9ff73153694c1d3 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrTAllocator_DEFINED
#define GrTAllocator_DEFINED
#include "src/gpu/GrBlockAllocator.h"
template <typename T, int StartingItems = 1>
class GrTAllocator {
public:
/**
* Create an allocator that defaults to using StartingItems as heap increment.
*/
GrTAllocator()
: fTotalCount(0)
, fAllocator(GrBlockAllocator::GrowthPolicy::kFixed) {}
/**
* Create an allocator
*
* @param itemsPerBlock the number of items to allocate at once
*/
explicit GrTAllocator(int itemsPerBlock)
: fTotalCount(0)
, fAllocator(GrBlockAllocator::GrowthPolicy::kFixed,
GrBlockAllocator::BlockOverhead<alignof(T)>() + sizeof(T)*itemsPerBlock) {}
~GrTAllocator() { this->reset(); }
/**
* Adds an item and returns it.
*
* @return the added item.
*/
T& push_back() {
return *new (this->pushItem()) T;
}
T& push_back(const T& t) {
return *new (this->pushItem()) T(t);
}
T& push_back(T&& t) {
return *new (this->pushItem()) T(std::move(t));
}
template <typename... Args>
T& emplace_back(Args&&... args) {
return *new (this->pushItem()) T(std::forward<Args>(args)...);
}
/**
* Remove the last item, only call if count() != 0
*/
void pop_back() {
SkASSERT(fTotalCount > 0);
GrBlockAllocator::Block* block = fAllocator->currentBlock();
int newCount = block->metadata() - 1;
// Run dtor for the popped item
int releaseIndex;
GetItemAndOffset(block, newCount, &releaseIndex)->~T();
if (newCount == 0) {
fAllocator->releaseBlock(block);
} else {
// Since this always follows LIFO, the block should always be able to release the memory
SkAssertResult(block->release(releaseIndex, releaseIndex + sizeof(T)));
block->setMetadata(newCount);
}
fTotalCount--;
}
/**
* Removes all added items.
*/
void reset() {
// Invoke destructors in reverse order
for (const auto* b : fAllocator->rblocks()) {
int c = b->metadata();
for (int i = c - 1; i >= 0; i--) {
GetItem(b, i)->~T();
}
}
fAllocator->reset();
fTotalCount = 0;
}
/**
* Returns the item count.
*/
int count() const {
#ifdef SK_DEBUG
// Confirm total count matches sum of block counts
int count = 0;
int blockCount = 0;
for (const auto* b :fAllocator->blocks()) {
count += b->metadata();
blockCount++;
}
SkASSERT(count == fTotalCount);
#endif
return fTotalCount;
}
/**
* Is the count 0?
*/
bool empty() const { return this->count() == 0; }
/**
* Access first item, only call if count() != 0
*/
T& front() {
// This assumes that the head block actually have room to store the first item.
static_assert(StartingItems >= 1);
SkASSERT(fTotalCount > 0);
return *(GetItem(fAllocator->headBlock(), 0));
}
/**
* Access first item, only call if count() != 0
*/
const T& front() const {
SkASSERT(fTotalCount > 0);
return *(GetItem(fAllocator->headBlock(), 0));
}
/**
* Access last item, only call if count() != 0
*/
T& back() {
SkASSERT(fTotalCount > 0);
int blockCount = fAllocator->currentBlock()->metadata();
return *(GetItem(fAllocator->currentBlock(), blockCount - 1));
}
/**
* Access last item, only call if count() != 0
*/
const T& back() const {
SkASSERT(fTotalCount > 0);
int blockCount = fAllocator->currentBlock()->metadata();
return *(GetItem(fAllocator->currentBlock(), blockCount - 1));
}
template<bool Const>
class Iterator {
using BlockIter = typename GrBlockAllocator::BlockIter<true, Const>;
using C = typename std::conditional<Const, const T, T>::type;
using AllocatorT = typename std::conditional<Const, const GrTAllocator, GrTAllocator>::type;
public:
Iterator(AllocatorT* allocator) : fBlockIter(allocator->fAllocator.allocator()) {}
class Item {
public:
bool operator!=(const Item& other) const {
if (other.fBlock != fBlock) {
// Treat an empty head block the same as the end block
bool blockEmpty = !(*fBlock) || (*fBlock)->metadata() == 0;
bool otherEmpty = !(*other.fBlock) || (*other.fBlock)->metadata() == 0;
return !blockEmpty || !otherEmpty;
} else {
// Same block, so differentiate by index into it (unless it's the "end" block
// in which case ignore index).
return SkToBool(*fBlock) && other.fIndex != fIndex;
}
}
C& operator*() const {
C* item = const_cast<C*>(static_cast<const C*>((*fBlock)->ptr(fIndex)));
SkDEBUGCODE(int offset;)
SkASSERT(item == GetItemAndOffset(*fBlock, fItem, &offset) && offset == fIndex);
return *item;
}
Item& operator++() {
const auto* block = *fBlock;
fItem++;
fIndex += sizeof(T);
if (fItem >= block->metadata()) {
++fBlock;
fItem = 0;
fIndex = StartingIndex(fBlock);
}
return *this;
}
private:
friend Iterator;
using BlockItem = typename BlockIter::Item;
Item(BlockItem block) : fBlock(block), fItem(0), fIndex(StartingIndex(block)) {}
static int StartingIndex(const BlockItem& block) {
return *block ? (*block)->template firstAlignedOffset<alignof(T)>() : 0;
}
BlockItem fBlock;
int fItem;
int fIndex;
};
Item begin() const { return Item(fBlockIter.begin()); }
Item end() const { return Item(fBlockIter.end()); }
private:
const BlockIter fBlockIter;
};
using Iter = Iterator<false>;
using CIter = Iterator<true>;
/**
* Prefer using a for-range loop when iterating over all allocated items, vs. index access.
*/
Iter items() { return Iter(this); }
CIter items() const { return CIter(this); }
/**
* Access item by index. Not an operator[] since it should not be considered constant time.
*/
T& item(int i) {
// Iterate over blocks until we find the one that contains i.
SkASSERT(i >= 0 && i < fTotalCount);
for (const auto* b : fAllocator->blocks()) {
int blockCount = b->metadata();
if (i < blockCount) {
return *GetItem(b, i);
} else {
i -= blockCount;
}
}
SkUNREACHABLE;
}
const T& item(int i) const {
return const_cast<GrTAllocator<T>*>(this)->item(i);
}
private:
static constexpr size_t StartingSize =
GrBlockAllocator::Overhead<alignof(T)>() + StartingItems * sizeof(T);
static T* GetItemAndOffset(const GrBlockAllocator::Block* block, int index, int* offset) {
SkASSERT(index >= 0 && index < block->metadata());
*offset = block->firstAlignedOffset<alignof(T)>() + index * sizeof(T);
return const_cast<T*>(static_cast<const T*>(block->ptr(*offset)));
}
static T* GetItem(const GrBlockAllocator::Block* block, int index) {
int offset;
return GetItemAndOffset(block, index, &offset);
}
void* pushItem() {
// 'template' required because fAllocator is a template, calling a template member
auto br = fAllocator->template allocate<alignof(T)>(sizeof(T));
br.fBlock->setMetadata(br.fBlock->metadata() + 1);
fTotalCount++;
return br.fBlock->ptr(br.fAlignedOffset);
}
// Each Block in the allocator tracks their count of items, but it's convenient to store
// the sum of their counts as well.
int fTotalCount;
// N represents the number of items, whereas GrSBlockAllocator takes total bytes, so must
// account for the block allocator's size too.
GrSBlockAllocator<StartingSize> fAllocator;
};
#endif