blob: 049ab615f00b2f33ac0f5d6ce15252b23183fd5f [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* 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.
*/
#ifndef CHRE_UTIL_SEGMENTED_QUEUE_IMPL_H
#define CHRE_UTIL_SEGMENTED_QUEUE_IMPL_H
#include <type_traits>
#include <utility>
#include "chre/util/container_support.h"
#include "chre/util/segmented_queue.h"
namespace chre {
template <typename ElementType, size_t kBlockSize>
SegmentedQueue<ElementType, kBlockSize>::SegmentedQueue(size_t maxBlockCount,
size_t staticBlockCount)
: kMaxBlockCount(maxBlockCount), kStaticBlockCount(staticBlockCount) {
CHRE_ASSERT(kMaxBlockCount >= kStaticBlockCount);
CHRE_ASSERT(kStaticBlockCount > 0);
CHRE_ASSERT(kMaxBlockCount * kBlockSize < SIZE_MAX);
mRawStoragePtrs.reserve(kMaxBlockCount);
for (size_t i = 0; i < kStaticBlockCount; i++) {
pushOneBlock();
}
}
template <typename ElementType, size_t kBlockSize>
SegmentedQueue<ElementType, kBlockSize>::~SegmentedQueue() {
clear();
}
template <typename ElementType, size_t kBlockSize>
bool SegmentedQueue<ElementType, kBlockSize>::push_back(
const ElementType &element) {
if (!prepareForPush()) {
return false;
}
new (&locateDataAddress(mTail)) ElementType(element);
mSize++;
return true;
}
template <typename ElementType, size_t kBlockSize>
bool SegmentedQueue<ElementType, kBlockSize>::push_back(ElementType &&element) {
if (!prepareForPush()) {
return false;
}
new (&locateDataAddress(mTail)) ElementType(std::move(element));
mSize++;
return true;
}
template <typename ElementType, size_t kBlockSize>
bool SegmentedQueue<ElementType, kBlockSize>::push(const ElementType &element) {
return push_back(element);
}
template <typename ElementType, size_t kBlockSize>
bool SegmentedQueue<ElementType, kBlockSize>::push(ElementType &&element) {
return push_back(std::move(element));
}
template <typename ElementType, size_t kBlockSize>
template <typename... Args>
bool SegmentedQueue<ElementType, kBlockSize>::emplace_back(Args &&...args) {
if (!prepareForPush()) {
return false;
}
new (&locateDataAddress(mTail)) ElementType(std::forward<Args>(args)...);
mSize++;
return true;
}
template <typename ElementType, size_t kBlockSize>
ElementType &SegmentedQueue<ElementType, kBlockSize>::operator[](size_t index) {
CHRE_ASSERT(index < mSize);
return locateDataAddress(relativeIndexToAbsolute(index));
}
template <typename ElementType, size_t kBlockSize>
const ElementType &SegmentedQueue<ElementType, kBlockSize>::operator[](
size_t index) const {
CHRE_ASSERT(index < mSize);
return locateDataAddress(relativeIndexToAbsolute(index));
}
template <typename ElementType, size_t kBlockSize>
ElementType &SegmentedQueue<ElementType, kBlockSize>::back() {
CHRE_ASSERT(!empty());
return locateDataAddress(mTail);
}
template <typename ElementType, size_t kBlockSize>
const ElementType &SegmentedQueue<ElementType, kBlockSize>::back() const {
CHRE_ASSERT(!empty());
return locateDataAddress(mTail);
}
template <typename ElementType, size_t kBlockSize>
ElementType &SegmentedQueue<ElementType, kBlockSize>::front() {
CHRE_ASSERT(!empty());
return locateDataAddress(mHead);
}
template <typename ElementType, size_t kBlockSize>
const ElementType &SegmentedQueue<ElementType, kBlockSize>::front() const {
CHRE_ASSERT(!empty());
return locateDataAddress(mHead);
}
template <typename ElementType, size_t kBlockSize>
void SegmentedQueue<ElementType, kBlockSize>::pop_front() {
CHRE_ASSERT(!empty());
doRemove(mHead);
if (mSize == 0) {
// TODO(b/258860898), Define a more proactive way to deallocate unused
// block.
resetEmptyQueue();
} else {
mHead = advanceOrWrapAround(mHead);
}
}
template <typename ElementType, size_t kBlockSize>
void SegmentedQueue<ElementType, kBlockSize>::pop() {
pop_front();
}
template <typename ElementType, size_t kBlockSize>
bool SegmentedQueue<ElementType, kBlockSize>::remove(size_t index) {
if (index >= mSize) {
return false;
}
size_t absoluteIndex = relativeIndexToAbsolute(index);
doRemove(absoluteIndex);
if (mSize == 0) {
resetEmptyQueue();
} else {
// TODO(b/258557394): optimize by adding check to see if pull from head
// to tail is more efficient.
moveElements(advanceOrWrapAround(absoluteIndex), absoluteIndex,
absoluteIndexToRelative(mTail) - index);
mTail = subtractOrWrapAround(mTail, /* steps= */ 1);
}
return true;
}
template <typename ElementType, size_t kBlockSize>
size_t SegmentedQueue<ElementType, kBlockSize>::searchMatches(
MatchingFunction *matchFunc, size_t foundIndicesLen,
size_t foundIndices[]) {
size_t foundCount = 0;
size_t searchIndex = advanceOrWrapAround(mTail);
bool firstRound = true;
if (size() == 0) {
return 0;
}
// firstRound need to be checked since if the queue is full, the index after
// mTail will be mHead, leading to the loop falsely terminate in the first
// round.
while ((searchIndex != mHead || firstRound) &&
foundCount != foundIndicesLen) {
searchIndex = subtractOrWrapAround(searchIndex, 1 /* steps */);
firstRound = false;
if (matchFunc(locateDataAddress(searchIndex))) {
foundIndices[foundCount] = searchIndex;
++foundCount;
}
}
return foundCount;
}
template <typename ElementType, size_t kBlockSize>
void SegmentedQueue<ElementType, kBlockSize>::fillGaps(
size_t gapCount, const size_t gapIndices[]) {
if (gapCount == 0) {
return;
}
// Move the elements between each gap indices section by section from the
// section that is closest to the head. The destination index = the gap index
// - how many gaps has been filled.
//
// For instance, assuming we have elements that we want to remove (gaps) at
// these indices = [8, 7, 5, 2] and the last element is at index 10.
//
// The first iteration will move the items at index 3, 4, which is the first
// section, to index 2, 3 and overwrite the original item at index 2, making
// the queue: [0, 1, 3, 4, x, 5, 6, ...., 10] where x means empty slot.
//
// The second iteration will do a similar thing, move item 6 to the empty
// slot, which could be calculated by using the index of the last gap and how
// many gaps has been filled. So the queue turns into:
// [0, 1, 3, 4, 6, x, x, 7, 8, 9, 10], note that there are now two empty slots
// since there are two gaps filled.
//
// The third iteration does not move anything since there are no items between
// 7 and 8.
//
// The final iteration is a special case to close the final gap. After the
// final iteration, the queue will become: [1, 3, 4, 6, 9, 10].
for (size_t i = gapCount - 1; i > 0; --i) {
moveElements(advanceOrWrapAround(gapIndices[i]),
subtractOrWrapAround(gapIndices[i], gapCount - 1 - i),
absoluteIndexToRelative(gapIndices[i - 1]) -
absoluteIndexToRelative(gapIndices[i]) - 1);
}
// Since mTail is not guaranteed to be a gap, we need to make a special case
// for moving the last section.
moveElements(
advanceOrWrapAround(gapIndices[0]),
subtractOrWrapAround(gapIndices[0], gapCount - 1),
absoluteIndexToRelative(mTail) - absoluteIndexToRelative(gapIndices[0]));
mTail = subtractOrWrapAround(mTail, gapCount);
}
template <typename ElementType, size_t kBlockSize>
size_t SegmentedQueue<ElementType, kBlockSize>::removeMatchedFromBack(
MatchingFunction *matchFunc, size_t maxNumOfElementsRemoved,
FreeFunction *freeFunction, void *extraDataForFreeFunction) {
size_t removeIndex[maxNumOfElementsRemoved];
size_t removedItemCount =
searchMatches(matchFunc, maxNumOfElementsRemoved, removeIndex);
if (removedItemCount != 0) {
for (size_t i = 0; i < removedItemCount; ++i) {
if (freeFunction == nullptr) {
doRemove(removeIndex[i]);
} else {
--mSize;
freeFunction(locateDataAddress(removeIndex[i]),
extraDataForFreeFunction);
}
}
if (mSize == 0) {
resetEmptyQueue();
} else {
fillGaps(removedItemCount, removeIndex);
}
}
return removedItemCount;
}
template <typename ElementType, size_t kBlockSize>
bool SegmentedQueue<ElementType, kBlockSize>::pushOneBlock() {
return insertBlock(mRawStoragePtrs.size());
}
template <typename ElementType, size_t kBlockSize>
bool SegmentedQueue<ElementType, kBlockSize>::insertBlock(size_t blockIndex) {
// Supporting inserting at any index since we started this data structure as
// std::deque and would like to support push_front() in the future. This
// function should not be needed once b/258771255 is implemented.
CHRE_ASSERT(mRawStoragePtrs.size() != kMaxBlockCount);
bool success = false;
Block *newBlockPtr = static_cast<Block *>(memoryAlloc(sizeof(Block)));
if (newBlockPtr != nullptr) {
success = mRawStoragePtrs.insert(blockIndex, UniquePtr(newBlockPtr));
if (success) {
if (!empty() && mHead >= blockIndex * kBlockSize) {
// If we are inserting a block before the current mHead, we need to
// increase the offset since we now have a new gap from mHead to the
// head of the container.
mHead += kBlockSize;
}
// If mTail is after the new block, we want to move mTail to make sure
// that the queue is continuous.
if (mTail >= blockIndex * kBlockSize) {
moveElements((blockIndex + 1) * kBlockSize, blockIndex * kBlockSize,
(mTail + 1) % kBlockSize);
}
}
}
if (!success) {
LOG_OOM();
}
return success;
}
template <typename ElementType, size_t kBlockSize>
void SegmentedQueue<ElementType, kBlockSize>::moveElements(size_t srcIndex,
size_t destIndex,
size_t count) {
// TODO(b/259281024): Make sure SegmentedQueue::moveElement() does not
// incorrectly overwrites elements.
while (count--) {
doMove(srcIndex, destIndex,
typename std::is_move_constructible<ElementType>::type());
srcIndex = advanceOrWrapAround(srcIndex);
destIndex = advanceOrWrapAround(destIndex);
}
}
template <typename ElementType, size_t kBlockSize>
void SegmentedQueue<ElementType, kBlockSize>::doMove(size_t srcIndex,
size_t destIndex,
std::true_type) {
new (&locateDataAddress(destIndex))
ElementType(std::move(locateDataAddress(srcIndex)));
}
template <typename ElementType, size_t kBlockSize>
void SegmentedQueue<ElementType, kBlockSize>::doMove(size_t srcIndex,
size_t destIndex,
std::false_type) {
new (&locateDataAddress(destIndex)) ElementType(locateDataAddress(srcIndex));
}
template <typename ElementType, size_t kBlockSize>
size_t SegmentedQueue<ElementType, kBlockSize>::relativeIndexToAbsolute(
size_t index) {
size_t absoluteIndex = mHead + index;
if (absoluteIndex >= capacity()) {
absoluteIndex -= capacity();
}
return absoluteIndex;
}
template <typename ElementType, size_t kBlockSize>
size_t SegmentedQueue<ElementType, kBlockSize>::absoluteIndexToRelative(
size_t index) {
if (mHead > index) {
index += capacity();
}
return index - mHead;
}
template <typename ElementType, size_t kBlockSize>
bool SegmentedQueue<ElementType, kBlockSize>::prepareForPush() {
bool success = false;
if (full()) {
LOG_OOM();
} else {
if (mSize == capacity()) {
// TODO(b/258771255): index-based insert block should go away when we
// have a ArrayQueue based container.
size_t insertBlockIndex = (mTail + 1) / kBlockSize;
success = insertBlock(insertBlockIndex);
} else {
success = true;
}
if (success) {
mTail = advanceOrWrapAround(mTail);
}
}
return success;
}
template <typename ElementType, size_t kBlockSize>
void SegmentedQueue<ElementType, kBlockSize>::clear() {
if (!std::is_trivially_destructible<ElementType>::value) {
while (!empty()) {
pop_front();
}
} else {
mSize = 0;
mHead = 0;
mTail = capacity() - 1;
}
}
template <typename ElementType, size_t kBlockSize>
ElementType &SegmentedQueue<ElementType, kBlockSize>::locateDataAddress(
size_t index) {
return mRawStoragePtrs[index / kBlockSize].get()->data()[index % kBlockSize];
}
template <typename ElementType, size_t kBlockSize>
size_t SegmentedQueue<ElementType, kBlockSize>::advanceOrWrapAround(
size_t index) {
return index >= capacity() - 1 ? 0 : index + 1;
}
template <typename ElementType, size_t kBlockSize>
size_t SegmentedQueue<ElementType, kBlockSize>::subtractOrWrapAround(
size_t index, size_t steps) {
return index < steps ? capacity() + index - steps : index - steps;
}
template <typename ElementType, size_t kBlockSize>
void SegmentedQueue<ElementType, kBlockSize>::doRemove(size_t index) {
mSize--;
locateDataAddress(index).~ElementType();
}
template <typename ElementType, size_t kBlockSize>
void SegmentedQueue<ElementType, kBlockSize>::resetEmptyQueue() {
CHRE_ASSERT(empty());
while (mRawStoragePtrs.size() != kStaticBlockCount) {
mRawStoragePtrs.pop_back();
}
mHead = 0;
mTail = capacity() - 1;
}
} // namespace chre
#endif // CHRE_UTIL_SEGMENTED_QUEUE_IMPL_H