| /* | |
| * 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_ |