blob: f9fe67aaa8d735384e60369294f4d8ec4956333c [file] [log] [blame]
/*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2013 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* Defines a memory pool class BitMapObjectPool.
*/
#pragma once
#include <array>
#include <assert.h>
#include <atomic>
#include <limits>
#include <new>
#include <stddef.h>
namespace chip {
class StaticAllocatorBase
{
public:
StaticAllocatorBase(size_t capacity) : mAllocated(0), mCapacity(capacity) {}
size_t Capacity() const { return mCapacity; }
size_t Allocated() const { return mAllocated; }
bool Exhausted() const { return mAllocated == mCapacity; }
protected:
size_t mAllocated;
const size_t mCapacity;
};
class StaticAllocatorBitmap : public StaticAllocatorBase
{
protected:
/**
* Use the largest data type supported by `std::atomic`. Putting multiple atomic inside a single cache line won't improve
* concurrency, while the use of larger data type can improve the performance by reducing the number of outer loop iterations.
*/
using tBitChunkType = unsigned long;
static constexpr const tBitChunkType kBit1 = 1; // make sure bitshifts produce the right type
static constexpr const size_t kBitChunkSize = std::numeric_limits<tBitChunkType>::digits;
static_assert(ATOMIC_LONG_LOCK_FREE, "StaticAllocatorBitmap is not lock free");
public:
StaticAllocatorBitmap(void * storage, std::atomic<tBitChunkType> * usage, size_t capacity, size_t elementSize);
void * Allocate();
void Deallocate(void * element);
protected:
void * At(size_t index) { return static_cast<uint8_t *>(mElements) + mElementSize * index; }
size_t IndexOf(void * element);
using Lambda = bool (*)(void *, void *);
bool ForEachActiveObjectInner(void * context, Lambda lambda);
private:
void * mElements;
const size_t mElementSize;
std::atomic<tBitChunkType> * mUsage;
};
/**
* @brief
* A class template used for allocating Objects.
*
* @tparam T a subclass of element to be allocated.
* @tparam N a positive integer max number of elements the pool provides.
*/
template <class T, size_t N>
class BitMapObjectPool : public StaticAllocatorBitmap
{
public:
BitMapObjectPool() : StaticAllocatorBitmap(mData.mMemory, mUsage, N, sizeof(T)) {}
static size_t Size() { return N; }
template <typename... Args>
T * CreateObject(Args &&... args)
{
T * element = static_cast<T *>(Allocate());
if (element != nullptr)
return new (element) T(std::forward<Args>(args)...);
else
return nullptr;
}
void ReleaseObject(T * element)
{
if (element == nullptr)
return;
element->~T();
Deallocate(element);
}
template <typename... Args>
void ResetObject(T * element, Args &&... args)
{
element->~T();
new (element) T(std::forward<Args>(args)...);
}
/**
* @brief
* Run a functor for each active object in the pool
*
* @param function The functor of type `bool (*)(T*)`, return false to break the iteration
* @return bool Returns false if broke during iteration
*
* caution
* this function is not thread-safe, make sure all usage of the
* pool is protected by a lock, or else avoid using this function
*/
template <typename Function>
bool ForEachActiveObject(Function && function)
{
LambdaProxy<Function> proxy(std::forward<Function>(function));
return ForEachActiveObjectInner(&proxy, &LambdaProxy<Function>::Call);
}
private:
template <typename Function>
class LambdaProxy
{
public:
LambdaProxy(Function && function) : mFunction(std::move(function)) {}
static bool Call(void * context, void * target)
{
return static_cast<LambdaProxy *>(context)->mFunction(static_cast<T *>(target));
}
private:
Function mFunction;
};
std::atomic<tBitChunkType> mUsage[(N + kBitChunkSize - 1) / kBitChunkSize];
union Data
{
Data() {}
~Data() {}
alignas(alignof(T)) uint8_t mMemory[N * sizeof(T)];
T mMemoryViewForDebug[N]; // Just for debugger
} mData;
};
} // namespace chip