| // Copyright 2025 The Pigweed 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 |
| // |
| // https://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. |
| |
| #include <cstring> |
| #include <utility> |
| |
| #include "pw_assert/check.h" |
| #include "pw_containers/algorithm.h" |
| #include "pw_multibuf/v2/internal/byte_iterator.h" |
| #include "pw_multibuf/v2/multibuf.h" |
| #include "pw_status/try.h" |
| |
| namespace pw::multibuf::v2::internal { |
| |
| template <typename Out> |
| static constexpr Out CheckedCast(size_t val) { |
| size_t max = size_t(std::numeric_limits<Out>::max()); |
| PW_CHECK_UINT_LE(val, max); |
| return static_cast<Out>(val); |
| } |
| |
| GenericMultiBuf& GenericMultiBuf::operator=(GenericMultiBuf&& other) { |
| Clear(); |
| deque_ = std::move(other.deque_); |
| entries_per_chunk_ = |
| std::exchange(other.entries_per_chunk_, Entry::kMinEntriesPerChunk); |
| observer_ = std::exchange(other.observer_, nullptr); |
| return *this; |
| } |
| |
| bool GenericMultiBuf::TryReserveForInsert(const_iterator pos, |
| const GenericMultiBuf& mb) { |
| size_type entries_per_chunk = entries_per_chunk_; |
| while (entries_per_chunk_ < mb.entries_per_chunk_) { |
| if (!AddLayer(0)) { |
| break; |
| } |
| } |
| if (pos.offset_ == 0 || !IsOwned(pos.chunk_) || |
| TryConvertToShared(pos.chunk_)) { |
| if (entries_per_chunk_ >= mb.entries_per_chunk_ && |
| TryReserveEntries(entries_per_chunk_ * mb.num_chunks(), |
| pos.offset_ != 0)) { |
| return true; |
| } |
| } |
| while (entries_per_chunk_ > entries_per_chunk) { |
| PopLayer(); |
| } |
| return false; |
| } |
| |
| bool GenericMultiBuf::TryReserveForInsert(const_iterator pos) { |
| return (pos.offset_ == 0 || !IsOwned(pos.chunk_) || |
| TryConvertToShared(pos.chunk_)) && |
| TryReserveEntries(entries_per_chunk_, pos.offset_ != 0); |
| } |
| |
| auto GenericMultiBuf::Insert(const_iterator pos, GenericMultiBuf&& mb) |
| -> iterator { |
| // Make room for the other object's entries. |
| PW_CHECK(TryReserveForInsert(pos, mb)); |
| size_type chunk = InsertChunks(pos, mb.num_chunks()); |
| |
| // Merge the entries into this object. |
| size_t size = 0; |
| size_type index = chunk * entries_per_chunk_; |
| while (!mb.empty()) { |
| size_type i = 0; |
| size_type offset = mb.GetOffset(0); |
| size_type length = mb.GetLength(0); |
| for (; i < mb.entries_per_chunk_; ++i) { |
| deque_[index + i] = mb.deque_.front(); |
| mb.deque_.pop_front(); |
| } |
| |
| // If this object is deeper than `mb`, pad it with extra entries. |
| for (; i < entries_per_chunk_; ++i) { |
| deque_[index + i].view = { |
| .offset = offset, |
| .sealed = false, |
| .length = length, |
| .boundary = true, |
| }; |
| } |
| size += size_t{length}; |
| index += entries_per_chunk_; |
| } |
| if (mb.observer_ != nullptr) { |
| mb.observer_->Notify(Observer::Event::kBytesRemoved, size); |
| } |
| if (observer_ != nullptr) { |
| observer_->Notify(Observer::Event::kBytesAdded, size); |
| } |
| return MakeIterator(chunk); |
| } |
| |
| auto GenericMultiBuf::Insert(const_iterator pos, ConstByteSpan bytes) |
| -> iterator { |
| PW_CHECK(TryReserveForInsert(pos)); |
| [[maybe_unused]] auto [iter, unused] = Insert(pos, bytes, 0, bytes.size()); |
| return iter; |
| } |
| |
| auto GenericMultiBuf::Insert(const_iterator pos, |
| UniquePtr<const std::byte[]>&& owned) -> iterator { |
| ConstByteSpan bytes(owned.get(), owned.size()); |
| PW_CHECK(TryReserveForInsert(pos)); |
| auto [iter, chunk] = Insert(pos, bytes, 0, bytes.size()); |
| if (owned == nullptr) { |
| return iter; |
| } |
| deque_[memory_context_index(chunk)].deallocator = owned.deallocator(); |
| deque_[base_view_index(chunk)].base_view.owned = true; |
| owned.Release(); |
| return iter; |
| } |
| |
| auto GenericMultiBuf::Insert(const_iterator pos, |
| const SharedPtr<const std::byte[]>& shared, |
| size_t offset, |
| size_t length) -> iterator { |
| ConstByteSpan bytes(shared.get(), shared.size()); |
| PW_CHECK(TryReserveForInsert(pos)); |
| auto [iter, chunk] = Insert(pos, bytes, offset, length); |
| if (shared == nullptr) { |
| return iter; |
| } |
| |
| // Extract the shared pointer's control block using a restricted method. |
| const auto& handle = ControlBlockHandle::GetInstance_DO_NOT_USE(); |
| ControlBlock* control_block = shared.GetControlBlock(handle); |
| |
| deque_[memory_context_index(chunk)].control_block = control_block; |
| deque_[base_view_index(chunk)].base_view.shared = true; |
| control_block->IncrementShared(); |
| return iter; |
| } |
| |
| Result<GenericMultiBuf> GenericMultiBuf::Remove(const_iterator pos, |
| size_t size) { |
| PW_CHECK(IsRemovable(pos, size)); |
| GenericMultiBuf out(deque_.get_allocator()); |
| if (!TryReserveForRemove(pos, size, &out)) { |
| return Status::ResourceExhausted(); |
| } |
| MoveRange(pos, size, out); |
| if (observer_ != nullptr) { |
| observer_->Notify(Observer::Event::kBytesRemoved, size); |
| } |
| return Result<GenericMultiBuf>(std::move(out)); |
| } |
| |
| Result<GenericMultiBuf> GenericMultiBuf::PopFrontFragment() { |
| PW_CHECK(!empty()); |
| size_t size = 0; |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| size_type length = GetLength(chunk); |
| if (length == 0) { |
| continue; |
| } |
| size += size_t{length}; |
| if (IsBoundary(chunk)) { |
| break; |
| } |
| } |
| return Remove(begin(), size); |
| } |
| |
| Result<GenericMultiBuf::const_iterator> GenericMultiBuf::Discard( |
| const_iterator pos, size_t size) { |
| if (!TryReserveForRemove(pos, size, nullptr)) { |
| return Status::ResourceExhausted(); |
| } |
| difference_type out_offset = pos - begin(); |
| ClearRange(pos, size); |
| if (observer_ != nullptr) { |
| observer_->Notify(Observer::Event::kBytesRemoved, size); |
| } |
| return cbegin() + out_offset; |
| } |
| |
| bool GenericMultiBuf::IsReleasable(const_iterator pos) const { |
| PW_CHECK(pos != cend()); |
| return IsOwned(pos.chunk_); |
| } |
| |
| UniquePtr<std::byte[]> GenericMultiBuf::Release(const_iterator pos) { |
| PW_CHECK(IsReleasable(pos)); |
| ByteSpan bytes = GetView(pos.chunk_, 1); |
| Deallocator& deallocator = GetDeallocator(pos.chunk_); |
| EraseRange(pos - pos.offset_, size_t{GetLength(pos.chunk_)}); |
| if (observer_ != nullptr) { |
| observer_->Notify(Observer::Event::kBytesRemoved, bytes.size()); |
| } |
| return UniquePtr<std::byte[]>(bytes.data(), bytes.size(), deallocator); |
| } |
| |
| bool GenericMultiBuf::IsShareable(const_iterator pos) const { |
| PW_CHECK(pos != cend()); |
| return IsShared(pos.chunk_); |
| } |
| |
| SharedPtr<std::byte[]> GenericMultiBuf::Share(const_iterator pos) const { |
| PW_CHECK(IsShareable(pos)); |
| ControlBlock& control_block = GetControlBlock(pos.chunk_); |
| control_block.IncrementShared(); |
| return SharedPtr<std::byte[]>(GetData(pos.chunk_), &control_block); |
| } |
| |
| size_t GenericMultiBuf::CopyFrom(ConstByteSpan src, size_t offset) { |
| size_t total = 0; |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| if (src.empty()) { |
| break; |
| } |
| ByteSpan view = GetView(chunk); |
| if (offset < view.size()) { |
| size_t size = std::min(view.size() - offset, src.size()); |
| std::memcpy(view.data() + offset, src.data(), size); |
| src = src.subspan(size); |
| offset = 0; |
| total += size; |
| } else { |
| offset -= view.size(); |
| } |
| } |
| return total; |
| } |
| |
| ConstByteSpan GenericMultiBuf::Get(ByteSpan copy, size_t offset) const { |
| ByteSpan buffer; |
| std::optional<size_type> start; |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| ByteSpan view = GetView(chunk); |
| if (buffer.empty() && offset >= view.size()) { |
| // Still looking for start of data. |
| offset -= view.size(); |
| } else if (buffer.empty()) { |
| // Found the start of data. |
| buffer = view.subspan(offset); |
| start = chunk; |
| } else if (buffer.data() + buffer.size() == view.data()) { |
| // Current view is contiguous with previous; append. |
| buffer = ByteSpan(buffer.data(), buffer.size() + view.size()); |
| } else { |
| // Span is discontiguous and needs to be copied. |
| size_t copied = CopyToImpl(copy, offset, start.value()); |
| return copy.subspan(0, copied); |
| } |
| } |
| // Requested span is contiguous and can be directly passed to the visitor. |
| return buffer.size() <= copy.size() ? buffer : buffer.subspan(0, copy.size()); |
| } |
| |
| void GenericMultiBuf::Clear() { |
| while (num_layers() > 1) { |
| UnsealTopLayer(); |
| PopLayer(); |
| } |
| size_t num_bytes = size(); |
| ClearRange(begin(), num_bytes); |
| if (observer_ != nullptr) { |
| observer_->Notify(Observer::Event::kBytesRemoved, num_bytes); |
| observer_ = nullptr; |
| } |
| } |
| |
| void GenericMultiBuf::ShrinkToFit() { deque_.shrink_to_fit(); } |
| |
| bool GenericMultiBuf::TryReserveLayers(size_t num_layers, size_t num_chunks) { |
| if (num_layers == 0 || num_chunks == 0) { |
| return true; |
| } |
| size_type num_entries = 0; |
| PW_CHECK(CheckedIncrement(num_layers, Entry::kMinEntriesPerChunk - 1)); |
| PW_CHECK(CheckedMul(num_layers, num_chunks, num_entries)); |
| if (num_entries <= deque_.size()) { |
| return true; |
| } |
| return TryReserveEntries(num_entries - deque_.size()); |
| } |
| |
| bool GenericMultiBuf::AddLayer(size_t offset, size_t length) { |
| // Given entries with layers A and B, to which we want to add layer C: |
| // A1 B1 A2 B2 A3 B3 A4 B4 |
| // 1). Add `shift` empty buffers: |
| // A1 B1 A2 B2 A3 B3 A4 B4 -- -- -- -- |
| size_type shift = num_chunks(); |
| if (!TryReserveEntries(shift)) { |
| return false; |
| } |
| ++entries_per_chunk_; |
| for (size_t i = 0; i < shift; ++i) { |
| deque_.push_back({.data = nullptr}); |
| } |
| |
| // 2). Shift the existing layers over. This is expensive, but slicing usually |
| // happens with `shift == 1`: |
| // A1 B1 -- A2 B2 -- A3 B3 -- A4 B4 -- |
| for (size_type i = deque_.size(); i != 0; --i) { |
| if (i % entries_per_chunk_ == 0) { |
| --shift; |
| deque_[i - 1].view = { |
| .offset = 0, |
| .sealed = false, |
| .length = 0, |
| .boundary = false, |
| }; |
| } else { |
| deque_[i - 1] = deque_[i - 1 - shift]; |
| } |
| } |
| |
| // 3). Fill in the new layer C with subspans of layer B: |
| // A1 B1 C1 A2 B2 C2 A3 B3 C3 A4 B4 C4 |
| size_type off = CheckedCast<size_type>(offset); |
| size_type len = length == dynamic_extent ? 0 : CheckedCast<size_type>(length); |
| size_type lower_layer = num_layers() - 1; |
| size_type num_fragments = 0; |
| Entry* last = nullptr; |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| size_type lower_off = GetOffset(chunk, lower_layer); |
| size_type lower_len = GetLength(chunk, lower_layer); |
| |
| if (lower_len != 0 && IsBoundary(chunk, lower_layer)) { |
| ++num_fragments; |
| } |
| |
| // Skip over entries until we reach `offset`. |
| Entry& entry = deque_[top_view_index(chunk)]; |
| if (off >= lower_len) { |
| off -= lower_len; |
| entry.view.offset = 0; |
| entry.view.length = 0; |
| continue; |
| } |
| entry.view.offset = lower_off + off; |
| lower_len -= off; |
| |
| // This is similar to `entry.view.length = std::min(lower_len, len);`, but |
| // with extra cases to correctly handle `dynamic_extent` and boundaries. |
| if (lower_len == 0) { |
| entry.view.length = 0; |
| } else if (length == dynamic_extent) { |
| entry.view.length = lower_len; |
| last = &entry; |
| } else if (len == 0) { |
| entry.view.length = 0; |
| } else if (len <= lower_len) { |
| entry.view.length = len; |
| entry.view.boundary = true; |
| len = 0; |
| } else { |
| entry.view.length = lower_len; |
| len -= lower_len; |
| } |
| off = 0; |
| } |
| PW_CHECK_UINT_EQ(len, 0, "Requested layer exceeds available data"); |
| |
| if (last != nullptr) { |
| last->view.boundary = true; |
| } |
| if (observer_ != nullptr) { |
| observer_->Notify(Observer::Event::kLayerAdded, num_fragments); |
| } |
| return true; |
| } |
| |
| void GenericMultiBuf::SealTopLayer() { |
| PW_CHECK_UINT_GT(num_layers(), 1); |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| deque_[top_view_index(chunk)].view.sealed = true; |
| } |
| } |
| |
| void GenericMultiBuf::UnsealTopLayer() { |
| PW_CHECK_UINT_GT(num_layers(), 1); |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| deque_[top_view_index(chunk)].view.sealed = false; |
| } |
| } |
| |
| void GenericMultiBuf::TruncateTopLayer(size_t length) { |
| PW_CHECK_UINT_GT(num_layers(), 1); |
| |
| size_type len = CheckedCast<size_type>(length); |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| PW_CHECK(!IsSealed(chunk), |
| "MultiBuf::TruncateTopLayer() was called on a sealed layer; call " |
| "UnsealTopLayer first"); |
| size_type chunk_len = GetLength(chunk); |
| if (len >= chunk_len) { |
| len -= chunk_len; |
| continue; |
| } |
| deque_[top_view_index(chunk)].view.length = len; |
| len = 0; |
| } |
| PW_CHECK_UINT_EQ(len, |
| 0, |
| "MultiBuf::TruncateTopLayer() was called with a length " |
| "longer than the MultiBuf"); |
| } |
| |
| void GenericMultiBuf::PopLayer() { |
| PW_CHECK_UINT_GT(num_layers(), 1); |
| |
| // Given entries with layers A, B, and C, to remove layer C: |
| // A1 B1 C1 A2 B2 C2 A3 B3 C3 A4 B4 C4 |
| // 1). Check that the layer is not sealed. |
| size_t num_fragments = 0; |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| PW_CHECK(!IsSealed(chunk), |
| "MultiBuf::PopLayer() was called on a sealed layer; call " |
| "UnsealTopLayer first"); |
| if (GetLength(chunk) != 0 && IsBoundary(chunk)) { |
| ++num_fragments; |
| } |
| } |
| |
| // 2). Compress lower layers backward. |
| // -- -- -- -- A1 B1 A2 B2 A3 B3 A4 B4 |
| size_type shift = 0; |
| size_type discard = deque_.size() / entries_per_chunk_; |
| size_type keep = deque_.size() - discard; |
| --entries_per_chunk_; |
| for (size_type i = 1; i <= keep; ++i) { |
| size_type j = deque_.size() - i; |
| if ((i - 1) % entries_per_chunk_ == 0) { |
| ++shift; |
| } |
| deque_[j] = deque_[j - shift]; |
| if ((j - discard) % entries_per_chunk_ != entries_per_chunk_ - 1) { |
| continue; |
| } |
| } |
| |
| // 3). Discard the first elements |
| // A1 B1 A2 B2 A3 B3 A4 B4 |
| for (size_type i = 0; i < discard; ++i) { |
| deque_.pop_front(); |
| } |
| if (observer_ != nullptr) { |
| observer_->Notify(Observer::Event::kLayerRemoved, num_fragments); |
| } |
| } |
| |
| // Implementation methods |
| |
| size_t GenericMultiBuf::CheckRange(size_t offset, size_t length, size_t size) { |
| PW_CHECK_UINT_LE(size, Entry::kMaxSize); |
| PW_CHECK_UINT_LE(offset, size); |
| if (length == dynamic_extent) { |
| return size - offset; |
| } |
| PW_CHECK_UINT_LE(length, size - offset); |
| return length; |
| } |
| |
| GenericMultiBuf::size_type GenericMultiBuf::NumFragments() const { |
| size_type num_fragments = 0; |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| if (GetLength(chunk) != 0 && IsBoundary(chunk)) { |
| ++num_fragments; |
| } |
| } |
| return num_fragments; |
| } |
| |
| bool GenericMultiBuf::TryConvertToShared(size_type chunk) { |
| Deallocator& deallocator = GetDeallocator(chunk); |
| std::byte* data = GetData(chunk); |
| Entry::BaseView& base_view = deque_[base_view_index(chunk)].base_view; |
| |
| // Create a new control block using a restricted method. |
| const auto& handle = ControlBlockHandle::GetInstance_DO_NOT_USE(); |
| auto* control_block = |
| ControlBlock::Create(handle, &deallocator, data, base_view.length); |
| |
| if (control_block == nullptr) { |
| return false; |
| } |
| deque_[memory_context_index(chunk)].control_block = control_block; |
| base_view.owned = false; |
| base_view.shared = true; |
| return true; |
| } |
| |
| bool GenericMultiBuf::TryReserveEntries(size_type num_entries, bool split) { |
| if (split) { |
| PW_CHECK(CheckedAdd(num_entries, entries_per_chunk_, num_entries)); |
| } |
| PW_CHECK(CheckedAdd(num_entries, deque_.size(), num_entries)); |
| return deque_.try_reserve_exact(num_entries); |
| } |
| |
| GenericMultiBuf::size_type GenericMultiBuf::InsertChunks(const_iterator pos, |
| size_type num_chunks) { |
| size_type chunk = pos.chunk_; |
| size_type offset = pos.offset_; |
| if (offset != 0) { |
| num_chunks++; |
| } else if (chunk < this->num_chunks()) { |
| // Insert before any empty chunks at `pos`. |
| while (chunk != 0 && GetLength(chunk - 1) == 0) { |
| --chunk; |
| } |
| } |
| size_type num_entries = num_chunks * entries_per_chunk_; |
| PW_CHECK(TryReserveEntries(num_entries, offset != 0)); |
| Entry entry; |
| entry.data = nullptr; |
| for (size_type i = 0; i < num_entries; ++i) { |
| deque_.push_back(entry); |
| } |
| size_type index = chunk * entries_per_chunk_; |
| for (size_type i = deque_.size() - 1; i >= index + num_entries; --i) { |
| deque_[i] = deque_[i - num_entries]; |
| } |
| |
| if (offset == 0) { |
| // New chunk falls between existing chunks. |
| return chunk; |
| } |
| // New chunk within an existing chunk, which must be split. |
| SplitAfter(chunk, offset, deque_, chunk + num_chunks); |
| SplitBefore(chunk, offset); |
| return chunk + 1; |
| } |
| |
| auto GenericMultiBuf::Insert(const_iterator pos, |
| ConstByteSpan bytes, |
| size_t offset, |
| size_t length) -> std::tuple<iterator, size_type> { |
| size_type chunk = InsertChunks(pos, 1); |
| deque_[memory_context_index(chunk)].deallocator = nullptr; |
| deque_[data_index(chunk)].data = const_cast<std::byte*>(bytes.data()); |
| auto off = CheckedCast<size_type>(offset); |
| if (length == dynamic_extent) { |
| length = bytes.size() - offset; |
| } |
| auto len = CheckedCast<size_type>(length); |
| deque_[base_view_index(chunk)].base_view = { |
| .offset = off, |
| .owned = false, |
| .length = len, |
| .shared = false, |
| }; |
| for (size_type layer = 2; layer <= num_layers(); ++layer) { |
| deque_[view_index(chunk, layer)].view = { |
| .offset = off, |
| .sealed = false, |
| .length = len, |
| .boundary = true, |
| }; |
| } |
| if (observer_ != nullptr) { |
| observer_->Notify(Observer::Event::kBytesAdded, length); |
| } |
| return std::make_tuple(MakeIterator(chunk), chunk); |
| } |
| |
| void GenericMultiBuf::SplitBase(size_type chunk, |
| Deque& out_deque, |
| size_type out_chunk) { |
| if (&deque_ == &out_deque && chunk == out_chunk) { |
| return; |
| } |
| PW_CHECK(!IsOwned(chunk)); |
| size_type index = chunk * entries_per_chunk_; |
| size_type out_index = out_chunk * entries_per_chunk_; |
| for (size_type i = 0; i < entries_per_chunk_; ++i) { |
| out_deque[out_index + i] = deque_[index + i]; |
| } |
| if (IsShared(chunk)) { |
| GetControlBlock(chunk).IncrementShared(); |
| } |
| } |
| |
| void GenericMultiBuf::SplitBefore(size_type chunk, |
| size_type split, |
| Deque& out_deque, |
| size_type out_chunk) { |
| SplitBase(chunk, out_deque, out_chunk); |
| split += GetOffset(chunk); |
| Entry::BaseView src_base_view = deque_[base_view_index(chunk)].base_view; |
| Entry::BaseView& dst_base_view = |
| out_deque[base_view_index(out_chunk)].base_view; |
| dst_base_view.offset = src_base_view.offset; |
| dst_base_view.length = split - src_base_view.offset; |
| for (size_type layer = 2; layer <= num_layers(); ++layer) { |
| Entry::View src_view = deque_[view_index(chunk, layer)].view; |
| Entry::View& dst_view = out_deque[view_index(out_chunk, layer)].view; |
| dst_view.offset = src_view.offset; |
| dst_view.length = split - src_view.offset; |
| } |
| } |
| |
| void GenericMultiBuf::SplitBefore(size_type chunk, size_type split) { |
| SplitBefore(chunk, split, deque_, chunk); |
| } |
| |
| void GenericMultiBuf::SplitAfter(size_type chunk, |
| size_type split, |
| Deque& out_deque, |
| size_type out_chunk) { |
| SplitBase(chunk, out_deque, out_chunk); |
| split += GetOffset(chunk); |
| Entry::BaseView src_base_view = deque_[base_view_index(chunk)].base_view; |
| Entry::BaseView& dst_base_view = |
| out_deque[base_view_index(out_chunk)].base_view; |
| dst_base_view.offset = split; |
| dst_base_view.length = src_base_view.offset + src_base_view.length - split; |
| for (size_type layer = 2; layer <= num_layers(); ++layer) { |
| Entry::View src_view = deque_[view_index(chunk, layer)].view; |
| Entry::View& dst_view = out_deque[view_index(out_chunk, layer)].view; |
| dst_view.offset = split; |
| dst_view.length = src_view.offset + src_view.length - split; |
| } |
| } |
| |
| void GenericMultiBuf::SplitAfter(size_type chunk, size_type split) { |
| SplitAfter(chunk, split, deque_, chunk); |
| } |
| |
| bool GenericMultiBuf::TryReserveForRemove(const_iterator pos, |
| size_t size, |
| GenericMultiBuf* out) { |
| PW_CHECK_UINT_NE(size, 0u); |
| auto end = pos + CheckedCast<difference_type>(size); |
| size_type shift = end.chunk_ - pos.chunk_; |
| |
| // If removing part of an owned chunk, make it shared. |
| if (pos.offset_ != 0 && IsOwned(pos.chunk_) && |
| !TryConvertToShared(pos.chunk_)) { |
| return false; |
| } |
| |
| // Removing a sub-chunk. |
| if (shift == 0 && pos.offset_ != 0) { |
| return (out == nullptr || out->TryReserveEntries(entries_per_chunk_)) && |
| TryReserveEntries(0, /*split=*/true); |
| } |
| |
| // If removing part of an owned chunk, make it shared. |
| if (end.offset_ != 0 && IsOwned(end.chunk_) && |
| !TryConvertToShared(end.chunk_)) { |
| return false; |
| } |
| |
| // Discarding entries, no room needed. |
| if (out == nullptr) { |
| return true; |
| } |
| |
| // Make room in `out`. |
| if (end.offset_ != 0) { |
| ++shift; |
| } |
| return out->TryReserveEntries(shift * entries_per_chunk_); |
| } |
| |
| void GenericMultiBuf::MoveRange(const_iterator pos, |
| size_t size, |
| GenericMultiBuf& out) { |
| size_type chunk = pos.chunk_; |
| size_type offset = pos.offset_; |
| auto end = pos + CheckedCast<difference_type>(size); |
| out.entries_per_chunk_ = entries_per_chunk_; |
| |
| // Determine how many entries needs to be moved. |
| size_type shift = end.chunk_ - chunk; |
| |
| // Are we removing the prefix of a single chunk? |
| if (shift == 0 && offset == 0) { |
| out.InsertChunks(begin(), 1); |
| SplitBefore(chunk, end.offset_, out.deque_, 0); |
| EraseRange(pos, size); |
| return; |
| } |
| |
| // Are we removing a sub-chunk? If so, split the chunk in two. |
| if (shift == 0) { |
| out.InsertChunks(begin(), 1); |
| SplitBefore(end.chunk_, end.offset_, out.deque_, 0); |
| out.SplitAfter(0, offset); |
| EraseRange(pos, size); |
| return; |
| } |
| |
| // Otherwise, start by copying entries to the new deque, if provided. |
| size_type out_chunk = 0; |
| size_type reserve = end.offset_ == 0 ? shift : shift + 1; |
| out.InsertChunks(cend(), reserve); |
| |
| // Move the suffix of the first chunk. |
| if (offset != 0) { |
| SplitAfter(chunk, offset, out.deque_, out_chunk); |
| --shift; |
| ++chunk; |
| ++out_chunk; |
| } |
| |
| // Move the complete chunks. |
| size_type index = chunk * entries_per_chunk_; |
| size_type end_index = end.chunk_ * entries_per_chunk_; |
| size_type out_index = out_chunk * entries_per_chunk_; |
| pw::copy(deque_.begin() + index, |
| deque_.begin() + end_index, |
| out.deque_.begin() + out_index); |
| chunk += shift; |
| out_chunk += shift; |
| |
| // Copy the prefix of the last chunk. |
| if (end.offset_ != 0) { |
| SplitBefore(end.chunk_, end.offset_, out.deque_, out_chunk); |
| } |
| EraseRange(pos, size); |
| } |
| |
| void GenericMultiBuf::ClearRange(const_iterator pos, size_t size) { |
| size_type chunk = pos.chunk_; |
| size_type offset = pos.offset_; |
| auto end = pos + CheckedCast<difference_type>(size); |
| if (offset != 0) { |
| ++chunk; |
| } |
| for (; chunk < end.chunk_; ++chunk) { |
| std::byte* data = GetData(chunk); |
| if (IsOwned(chunk)) { |
| Deallocator& deallocator = GetDeallocator(chunk); |
| deallocator.Deallocate(data); |
| continue; |
| } |
| if (!IsShared(chunk)) { |
| continue; |
| } |
| // To avoid races with other shared or weak pointers to the data, put the |
| // data pointer back into a SharedPtr and let it go out scope. |
| ControlBlock& control_block = GetControlBlock(chunk); |
| SharedPtr<std::byte[]> shared(data, &control_block); |
| } |
| EraseRange(pos, size); |
| } |
| |
| void GenericMultiBuf::EraseRange(const_iterator pos, size_t size) { |
| size_type chunk = pos.chunk_; |
| size_type offset = pos.offset_; |
| auto end = pos + CheckedCast<difference_type>(size); |
| |
| // Are we removing a sub-chunk? If so, split the chunk in two. |
| if (chunk == end.chunk_ && offset != 0) { |
| size_type new_chunk = InsertChunks(pos, 0); |
| SplitAfter(new_chunk, end.offset_ - offset); |
| return; |
| } |
| |
| // Discard suffix of first chunk. |
| if (offset != 0) { |
| SplitBefore(chunk, offset); |
| ++chunk; |
| } |
| |
| // Discard prefix of last chunk. |
| if (end.offset_ != 0) { |
| SplitAfter(end.chunk_, end.offset_); |
| } |
| |
| // Discard complete chunks. |
| if (chunk < end.chunk_) { |
| deque_.erase(deque_.begin() + (chunk * entries_per_chunk_), |
| deque_.begin() + (end.chunk_ * entries_per_chunk_)); |
| } |
| } |
| |
| size_t GenericMultiBuf::CopyToImpl(ByteSpan dst, |
| size_t offset, |
| size_type start) const { |
| size_t total = 0; |
| for (size_type chunk = start; chunk < num_chunks(); ++chunk) { |
| if (dst.empty()) { |
| break; |
| } |
| ConstByteSpan view = GetView(chunk); |
| if (offset < view.size()) { |
| size_t size = std::min(view.size() - offset, dst.size()); |
| std::memcpy(dst.data(), view.data() + offset, size); |
| dst = dst.subspan(size); |
| offset = 0; |
| total += size; |
| } else { |
| offset -= view.size(); |
| } |
| } |
| return total; |
| } |
| |
| bool GenericMultiBuf::IsTopLayerSealed() const { |
| for (size_type chunk = 0; chunk < num_chunks(); ++chunk) { |
| if (IsSealed(chunk)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace pw::multibuf::v2::internal |