blob: fa0d303943ef9f94493f9bfbdffa03e5eaab7fe9 [file] [log] [blame]
/*
* Copyright 2019 The libgav1 Authors
*
* 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 LIBGAV1_SRC_UTILS_UNBOUNDED_QUEUE_H_
#define LIBGAV1_SRC_UTILS_UNBOUNDED_QUEUE_H_
#include <cassert>
#include <cstddef>
#include <memory>
#include <new>
#include <utility>
#include "src/utils/compiler_attributes.h"
#include "src/utils/memory.h"
namespace libgav1 {
// A FIFO queue of an unbounded capacity.
//
// This implementation uses the general approach used in std::deque
// implementations. See, for example,
// https://stackoverflow.com/questions/6292332/what-really-is-a-deque-in-stl
//
// It is much simpler because it just needs to support the queue interface.
// The blocks are chained into a circular list, not managed by a "map". It
// does not shrink the internal buffer.
//
// An alternative implementation approach is a resizable circular array. See,
// for example, ResizingArrayQueue.java in https://algs4.cs.princeton.edu/code/
// and base::circular_deque in Chromium's base/containers library.
template <typename T>
class UnboundedQueue {
public:
UnboundedQueue() = default;
// Move only.
UnboundedQueue(UnboundedQueue&& other)
: first_block_(other.first_block_),
front_(other.front_),
last_block_(other.last_block_),
back_(other.back_) {
other.first_block_ = nullptr;
other.front_ = 0;
other.last_block_ = nullptr;
other.back_ = 0;
}
UnboundedQueue& operator=(UnboundedQueue&& other) {
if (this != &other) {
Destroy();
first_block_ = other.first_block_;
front_ = other.front_;
last_block_ = other.last_block_;
back_ = other.back_;
other.first_block_ = nullptr;
other.front_ = 0;
other.last_block_ = nullptr;
other.back_ = 0;
}
return *this;
}
~UnboundedQueue() { Destroy(); }
// Allocates two Blocks upfront because most access patterns require at
// least two Blocks. Returns false if the allocation of the Blocks failed.
LIBGAV1_MUST_USE_RESULT bool Init() {
std::unique_ptr<Block> new_block0(new (std::nothrow) Block);
std::unique_ptr<Block> new_block1(new (std::nothrow) Block);
if (new_block0 == nullptr || new_block1 == nullptr) return false;
first_block_ = last_block_ = new_block0.release();
new_block1->next = first_block_;
last_block_->next = new_block1.release();
return true;
}
// Checks if the queue has room for a new element. If the queue is full,
// tries to grow it. Returns false if the queue is full and the attempt to
// grow it failed.
//
// NOTE: GrowIfNeeded() must be called before each call to Push(). This
// inconvenient design is necessary to guarantee a successful Push() call.
//
// Push(T&& value) is often called with the argument std::move(value). The
// moved-from object |value| won't be usable afterwards, so it would be
// problematic if Push(T&& value) failed and we lost access to the original
// |value| object.
LIBGAV1_MUST_USE_RESULT bool GrowIfNeeded() {
assert(last_block_ != nullptr);
if (back_ == kBlockCapacity) {
if (last_block_->next == first_block_) {
// All Blocks are in use.
std::unique_ptr<Block> new_block(new (std::nothrow) Block);
if (new_block == nullptr) return false;
new_block->next = first_block_;
last_block_->next = new_block.release();
}
last_block_ = last_block_->next;
back_ = 0;
}
return true;
}
// Pushes the element |value| to the end of the queue. It is an error to call
// Push() when the queue is full.
void Push(const T& value) {
assert(last_block_ != nullptr);
assert(back_ < kBlockCapacity);
T* elements = reinterpret_cast<T*>(last_block_->buffer);
new (&elements[back_++]) T(value);
}
void Push(T&& value) {
assert(last_block_ != nullptr);
assert(back_ < kBlockCapacity);
T* elements = reinterpret_cast<T*>(last_block_->buffer);
new (&elements[back_++]) T(std::move(value));
}
// Returns the element at the front of the queue. It is an error to call
// Front() when the queue is empty.
T& Front() {
assert(!Empty());
T* elements = reinterpret_cast<T*>(first_block_->buffer);
return elements[front_];
}
const T& Front() const {
assert(!Empty());
T* elements = reinterpret_cast<T*>(first_block_->buffer);
return elements[front_];
}
// Removes the element at the front of the queue from the queue. It is an
// error to call Pop() when the queue is empty.
void Pop() {
assert(!Empty());
T* elements = reinterpret_cast<T*>(first_block_->buffer);
elements[front_++].~T();
if (front_ == kBlockCapacity) {
// The first block has become empty.
front_ = 0;
if (first_block_ == last_block_) {
// Only one Block is in use. Simply reset back_.
back_ = 0;
} else {
first_block_ = first_block_->next;
}
}
}
// Returns true if the queue is empty.
bool Empty() const { return first_block_ == last_block_ && front_ == back_; }
private:
// kBlockCapacity is the maximum number of elements each Block can hold.
// sizeof(void*) is subtracted from 2048 to account for the |next| pointer in
// the Block struct.
//
// In Linux x86_64, sizeof(std::function<void()>) is 32, so each Block can
// hold 63 std::function<void()> objects.
//
// NOTE: The corresponding value in <deque> in libc++ revision
// 245b5ba3448b9d3f6de5962066557e253a6bc9a4 is:
// template <class _ValueType, class _DiffType>
// struct __deque_block_size {
// static const _DiffType value =
// sizeof(_ValueType) < 256 ? 4096 / sizeof(_ValueType) : 16;
// };
//
// Note that 4096 / 256 = 16, so apparently this expression is intended to
// ensure the block size is at least 4096 bytes and each block can hold at
// least 16 elements.
static constexpr size_t kBlockCapacity =
(sizeof(T) < 128) ? (2048 - sizeof(void*)) / sizeof(T) : 16;
struct Block : public Allocable {
alignas(T) char buffer[kBlockCapacity * sizeof(T)];
Block* next;
};
void Destroy() {
if (first_block_ == nullptr) return; // An uninitialized queue.
// First free the unused blocks, which are located after last_block and
// before first_block_.
Block* block = last_block_->next;
// Cut the circular list open after last_block_.
last_block_->next = nullptr;
while (block != first_block_) {
Block* next = block->next;
delete block;
block = next;
}
// Then free the used blocks. Destruct the elements in the used blocks.
while (block != nullptr) {
const size_t begin = (block == first_block_) ? front_ : 0;
const size_t end = (block == last_block_) ? back_ : kBlockCapacity;
T* elements = reinterpret_cast<T*>(block->buffer);
for (size_t i = begin; i < end; ++i) {
elements[i].~T();
}
Block* next = block->next;
delete block;
block = next;
}
}
// Blocks are chained in a circular singly-linked list. If the list of Blocks
// is empty, both first_block_ and last_block_ are null pointers. If the list
// is nonempty, first_block_ points to the first used Block and last_block_
// points to the last used Block.
//
// Invariant: If Init() is called and succeeds, the queue is always nonempty.
// This allows all methods (except the destructor) to avoid null pointer
// checks for first_block_ and last_block_.
Block* first_block_ = nullptr;
// The index of the element in first_block_ to be removed by Pop().
size_t front_ = 0;
Block* last_block_ = nullptr;
// The index in last_block_ where the new element is inserted by Push().
size_t back_ = 0;
};
#if !LIBGAV1_CXX17
template <typename T>
constexpr size_t UnboundedQueue<T>::kBlockCapacity;
#endif
} // namespace libgav1
#endif // LIBGAV1_SRC_UTILS_UNBOUNDED_QUEUE_H_