blob: bc7cf60ca79b916494919749f45a3c864d74a468 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_UI_LIB_ESCHER_UTIL_OBJECT_POOL_H_
#define SRC_UI_LIB_ESCHER_UTIL_OBJECT_POOL_H_
#include <lib/syslog/cpp/macros.h>
#include <utility>
#include <vector>
namespace escher {
// Default policy for constructing and destroying ObjectPool objects. Each
// object is constructed and destroyed one-by-one.
//
// When replacing this policy with a different one, clients are free to do
// whatever they want as long as:
// - all 4 of these methods exist, since ObjectPool calls them.
// - a constructor is called before an allocated object is returned
// - a destructor is called before the pool is cleared/destroyed.
// - there is no double construction/destruction
template <typename T>
class DefaultObjectPoolPolicy {
public:
// Default construction policy is to use placement-new.
template <typename... Args>
inline void InitializePoolObject(T* ptr, Args&&... args) {
new (ptr) T(std::forward<Args>(args)...);
}
// Default destruction policy is to invoke the destructor in-place.
inline void DestroyPoolObject(T* ptr) { ptr->~T(); }
// Default block initialization policy is to do nothing; each object is
// constructed one-by-one via InitializePoolObject().
inline void InitializePoolObjectBlock(T* objects, size_t block_index, size_t num_objects) {}
// Default block destruction policy is to do nothing; each object is destroyed
// one-by-one via DestroyPoolObject().
inline void DestroyPoolObjectBlock(T* objects, size_t block_index, size_t num_objects) {}
};
// An ObjectPool is an allocator for objects of type T. The underlying memory
// is allocated in contiguous chunks. The default policy is to construct the
// objects as they are allocated (via InitializePoolObject()) and destroy them
// as they are freed (via DestroyPoolObject()). However, some objects such as
// Vulkan descriptor sets must be allocated in batches. For these cases, the
// ObjectPool can be parameterized with a different PolicyT object.
template <typename T, typename PolicyT = DefaultObjectPoolPolicy<T>>
class ObjectPool {
public:
template <typename... Args>
ObjectPool(Args&&... args) : policy_(std::forward<Args>(args)...) {}
~ObjectPool() { Clear(); }
// Allocate an object from the pool, constructing it with the specified
// arguments.
template <typename... Args>
T* Allocate(Args&&... args) {
if (vacants_.empty()) {
AllocateBlock();
}
T* ptr = vacants_.back();
vacants_.pop_back();
policy_.InitializePoolObject(ptr, std::forward<Args>(args)...);
return ptr;
}
// Free the object, releasing it back to the pool for subsequent re-use.
void Free(T* ptr) {
policy_.DestroyPoolObject(ptr);
vacants_.push_back(ptr);
}
// Return the number of objects that can be held in the initial block
// allocation.
static size_t InitialBlockSize() { return 64U; }
// Return the number of objects that can be held in the "block_index-th"
// allocation.
static size_t NumObjectsInBlock(size_t block_index) { return InitialBlockSize() << block_index; }
// Total number of objects that can be allocated from the pool without
// changing the amount of underlying memory.
size_t GetCapacity() const {
size_t num_objects = 0;
const size_t num_blocks = blocks_.size();
for (size_t i = 0; i < num_blocks; ++i) {
num_objects += NumObjectsInBlock(i);
}
return num_objects;
}
// Return the number of objects that have been allocated but not freed.
size_t UnfreedObjectCount() const { return GetCapacity() - vacants_.size(); }
// Release all pool resources. Illegal to call while there are still unfreed
// objects. ObjectPool only releases memory when Clear() is called.
void Clear() {
FX_DCHECK(UnfreedObjectCount() == 0);
const size_t num_blocks = blocks_.size();
for (size_t i = 0; i < num_blocks; ++i) {
const size_t num_objects = NumObjectsInBlock(i);
policy_.DestroyPoolObjectBlock(blocks_[i].get(), i, num_objects);
}
vacants_.clear();
blocks_.clear();
}
using PolicyType = PolicyT;
const PolicyType& policy() const { return policy_; }
private:
// Allocate a new block of objects, and add them all to |vacants_|. Called
// by Allocate() when |vacants_| is empty.
void AllocateBlock() {
const size_t block_index = blocks_.size();
const size_t num_objects = NumObjectsInBlock(block_index);
T* ptr = static_cast<T*>(malloc(num_objects * sizeof(T)));
blocks_.emplace_back(ptr);
policy_.InitializePoolObjectBlock(ptr, block_index, num_objects);
vacants_.reserve(vacants_.capacity() + num_objects);
for (size_t i = 0; i < num_objects; ++i) {
vacants_.push_back(ptr + i);
}
}
struct MallocDeleter {
void operator()(T* ptr) { ::free(ptr); }
};
PolicyT policy_;
std::vector<T*> vacants_;
std::vector<std::unique_ptr<T, MallocDeleter>> blocks_;
};
} // namespace escher
#endif // SRC_UI_LIB_ESCHER_UTIL_OBJECT_POOL_H_