blob: b879b846d27baca2d40ecb292158d4302d058e68 [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 GrAllocator_DEFINED
#define GrAllocator_DEFINED
#include "GrConfig.h"
#include "GrTypes.h"
#include "SkTArray.h"
#include "SkTypes.h"
#include <new>
class GrAllocator : SkNoncopyable {
public:
~GrAllocator() { this->reset(); }
/**
* Create an allocator
*
* @param itemSize the size of each item to allocate
* @param itemsPerBlock the number of items to allocate at once
* @param initialBlock optional memory to use for the first block.
* Must be at least itemSize*itemsPerBlock sized.
* Caller is responsible for freeing this memory.
*/
GrAllocator(size_t itemSize, int itemsPerBlock, void* initialBlock)
: fItemSize(itemSize)
, fItemsPerBlock(itemsPerBlock)
, fOwnFirstBlock(nullptr == initialBlock)
, fCount(0)
, fInsertionIndexInBlock(0) {
SkASSERT(itemsPerBlock > 0);
fBlockSize = fItemSize * fItemsPerBlock;
if (fOwnFirstBlock) {
// This force us to allocate a new block on push_back().
fInsertionIndexInBlock = fItemsPerBlock;
} else {
fBlocks.push_back() = initialBlock;
fInsertionIndexInBlock = 0;
}
}
/**
* Adds an item and returns pointer to it.
*
* @return pointer to the added item.
*/
void* push_back() {
// we always have at least one block
if (fItemsPerBlock == fInsertionIndexInBlock) {
fBlocks.push_back() = sk_malloc_throw(fBlockSize);
fInsertionIndexInBlock = 0;
}
void* ret = (char*)fBlocks.back() + fItemSize * fInsertionIndexInBlock;
++fCount;
++fInsertionIndexInBlock;
return ret;
}
/**
* Remove the last item, only call if count() != 0
*/
void pop_back() {
SkASSERT(fCount);
SkASSERT(fInsertionIndexInBlock > 0);
--fInsertionIndexInBlock;
--fCount;
if (0 == fInsertionIndexInBlock) {
// Never delete the first block
if (fBlocks.count() > 1) {
sk_free(fBlocks.back());
fBlocks.pop_back();
fInsertionIndexInBlock = fItemsPerBlock;
}
}
}
/**
* Removes all added items.
*/
void reset() {
int firstBlockToFree = fOwnFirstBlock ? 0 : 1;
for (int i = firstBlockToFree; i < fBlocks.count(); ++i) {
sk_free(fBlocks[i]);
}
if (fOwnFirstBlock) {
fBlocks.reset();
// This force us to allocate a new block on push_back().
fInsertionIndexInBlock = fItemsPerBlock;
} else {
fBlocks.pop_back_n(fBlocks.count() - 1);
fInsertionIndexInBlock = 0;
}
fCount = 0;
}
/**
* Returns the item count.
*/
int count() const {
return fCount;
}
/**
* Is the count 0?
*/
bool empty() const { return 0 == fCount; }
/**
* Access first item, only call if count() != 0
*/
void* front() {
SkASSERT(fCount);
SkASSERT(fInsertionIndexInBlock > 0);
return (char*)(fBlocks.front());
}
/**
* Access first item, only call if count() != 0
*/
const void* front() const {
SkASSERT(fCount);
SkASSERT(fInsertionIndexInBlock > 0);
return (const char*)(fBlocks.front());
}
/**
* Access last item, only call if count() != 0
*/
void* back() {
SkASSERT(fCount);
SkASSERT(fInsertionIndexInBlock > 0);
return (char*)(fBlocks.back()) + (fInsertionIndexInBlock - 1) * fItemSize;
}
/**
* Access last item, only call if count() != 0
*/
const void* back() const {
SkASSERT(fCount);
SkASSERT(fInsertionIndexInBlock > 0);
return (const char*)(fBlocks.back()) + (fInsertionIndexInBlock - 1) * fItemSize;
}
/**
* Iterates through the allocator. This is faster than using operator[] when walking linearly
* through the allocator.
*/
class Iter {
public:
/**
* Initializes the iterator. next() must be called before get().
*/
Iter(const GrAllocator* allocator)
: fAllocator(allocator)
, fBlockIndex(-1)
, fIndexInBlock(allocator->fItemsPerBlock - 1)
, fItemIndex(-1) {}
/**
* Advances the iterator. Iteration is finished when next() returns false.
*/
bool next() {
++fIndexInBlock;
++fItemIndex;
if (fIndexInBlock == fAllocator->fItemsPerBlock) {
++fBlockIndex;
fIndexInBlock = 0;
}
return fItemIndex < fAllocator->fCount;
}
/**
* Gets the current iterator value. Call next() at least once before calling. Don't call
* after next() returns false.
*/
void* get() const {
SkASSERT(fItemIndex >= 0 && fItemIndex < fAllocator->fCount);
return (char*) fAllocator->fBlocks[fBlockIndex] + fIndexInBlock * fAllocator->fItemSize;
}
private:
const GrAllocator* fAllocator;
int fBlockIndex;
int fIndexInBlock;
int fItemIndex;
};
/**
* Access item by index.
*/
void* operator[] (int i) {
SkASSERT(i >= 0 && i < fCount);
return (char*)fBlocks[i / fItemsPerBlock] +
fItemSize * (i % fItemsPerBlock);
}
/**
* Access item by index.
*/
const void* operator[] (int i) const {
SkASSERT(i >= 0 && i < fCount);
return (const char*)fBlocks[i / fItemsPerBlock] +
fItemSize * (i % fItemsPerBlock);
}
protected:
/**
* Set first block of memory to write into. Must be called before any other methods.
* This requires that you have passed nullptr in the constructor.
*
* @param initialBlock optional memory to use for the first block.
* Must be at least itemSize*itemsPerBlock sized.
* Caller is responsible for freeing this memory.
*/
void setInitialBlock(void* initialBlock) {
SkASSERT(0 == fCount);
SkASSERT(0 == fBlocks.count());
SkASSERT(fItemsPerBlock == fInsertionIndexInBlock);
fOwnFirstBlock = false;
fBlocks.push_back() = initialBlock;
fInsertionIndexInBlock = 0;
}
// For access to above function.
template <typename T> friend class GrTAllocator;
private:
static const int NUM_INIT_BLOCK_PTRS = 8;
SkSTArray<NUM_INIT_BLOCK_PTRS, void*, true> fBlocks;
size_t fBlockSize;
size_t fItemSize;
int fItemsPerBlock;
bool fOwnFirstBlock;
int fCount;
int fInsertionIndexInBlock;
typedef SkNoncopyable INHERITED;
};
template <typename T> class GrTAllocator;
template <typename T> void* operator new(size_t, GrTAllocator<T>*);
template <typename T> class GrTAllocator : SkNoncopyable {
public:
virtual ~GrTAllocator() { this->reset(); }
/**
* Create an allocator
*
* @param itemsPerBlock the number of items to allocate at once
*/
explicit GrTAllocator(int itemsPerBlock)
: fAllocator(sizeof(T), itemsPerBlock, nullptr) {}
/**
* Adds an item and returns it.
*
* @return the added item.
*/
T& push_back() {
void* item = fAllocator.push_back();
SkASSERT(item);
new (item) T;
return *(T*)item;
}
T& push_back(const T& t) {
void* item = fAllocator.push_back();
SkASSERT(item);
new (item) T(t);
return *(T*)item;
}
template <typename... Args> T& emplace_back(Args&&... args) {
void* item = fAllocator.push_back();
SkASSERT(item);
new (item) T(std::forward<Args>(args)...);
return *(T*)item;
}
/**
* Remove the last item, only call if count() != 0
*/
void pop_back() {
this->back().~T();
fAllocator.pop_back();
}
/**
* Removes all added items.
*/
void reset() {
int c = fAllocator.count();
for (int i = 0; i < c; ++i) {
((T*)fAllocator[i])->~T();
}
fAllocator.reset();
}
/**
* Returns the item count.
*/
int count() const {
return fAllocator.count();
}
/**
* Is the count 0?
*/
bool empty() const { return fAllocator.empty(); }
/**
* Access first item, only call if count() != 0
*/
T& front() {
return *(T*)fAllocator.front();
}
/**
* Access first item, only call if count() != 0
*/
const T& front() const {
return *(T*)fAllocator.front();
}
/**
* Access last item, only call if count() != 0
*/
T& back() {
return *(T*)fAllocator.back();
}
/**
* Access last item, only call if count() != 0
*/
const T& back() const {
return *(const T*)fAllocator.back();
}
/**
* Iterates through the allocator. This is faster than using operator[] when walking linearly
* through the allocator.
*/
class Iter {
public:
/**
* Initializes the iterator. next() must be called before get() or ops * and ->.
*/
Iter(const GrTAllocator* allocator) : fImpl(&allocator->fAllocator) {}
/**
* Advances the iterator. Iteration is finished when next() returns false.
*/
bool next() { return fImpl.next(); }
/**
* Gets the current iterator value. Call next() at least once before calling. Don't call
* after next() returns false.
*/
T* get() const { return (T*) fImpl.get(); }
/**
* Convenience operators. Same rules for calling apply as get().
*/
T& operator*() const { return *this->get(); }
T* operator->() const { return this->get(); }
private:
GrAllocator::Iter fImpl;
};
/**
* Access item by index.
*/
T& operator[] (int i) {
return *(T*)(fAllocator[i]);
}
/**
* Access item by index.
*/
const T& operator[] (int i) const {
return *(const T*)(fAllocator[i]);
}
protected:
/*
* Set first block of memory to write into. Must be called before any other methods.
*
* @param initialBlock optional memory to use for the first block.
* Must be at least size(T)*itemsPerBlock sized.
* Caller is responsible for freeing this memory.
*/
void setInitialBlock(void* initialBlock) {
fAllocator.setInitialBlock(initialBlock);
}
private:
friend void* operator new<T>(size_t, GrTAllocator*);
GrAllocator fAllocator;
typedef SkNoncopyable INHERITED;
};
template <int N, typename T> class GrSTAllocator : public GrTAllocator<T> {
private:
typedef GrTAllocator<T> INHERITED;
public:
GrSTAllocator() : INHERITED(N) {
this->setInitialBlock(fStorage.get());
}
private:
SkAlignedSTStorage<N, T> fStorage;
};
template <typename T> void* operator new(size_t size, GrTAllocator<T>* allocator) {
return allocator->fAllocator.push_back();
}
// Skia doesn't use C++ exceptions but it may be compiled with them enabled. Having an op delete
// to match the op new silences warnings about missing op delete when a constructor throws an
// exception.
template <typename T> void operator delete(void*, GrTAllocator<T>*) {
SK_ABORT("Invalid Operation");
}
#define GrNEW_APPEND_TO_ALLOCATOR(allocator_ptr, type_name, args) \
new (allocator_ptr) type_name args
#endif