mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36726 ## Changelog: [Internal] - This implements (together with unit tests) a generic container type, `BoundedConsumableBuffer` with the following properties: * It can only grow up to a specified max size * It's a circular buffer (the oldest elements are dropped if reached max size and adding a new element) * The entries can be "consumed" (once), which from the point of view of the consumer effectively clears the buffer * Even after the entries are consumed, all of the non-overwritten entries can still be independently retrieved an arbitrary amount of times The buffer effectively wraps around when adding new elements. Once an element added, it becomes "consumable" (or "observable") and remains such, until all of the current outstanding elements are explicitly "consumed". It doesn't get immediately removed from the buffer, however (if max buffer size allows), and can be later retrieved via the `getEntries` API, until eventually overwritten. {F928241747} The goal is to use it for buffering performance entries in the native WebPerformance implementation, where the model is that the performance entries should be both buffered/observable, but also for certain performance entry types (such as marks, measures and potentially certain event types) should be possible to retrieve all of them at any time (regardless of whether they have already been observed or not). In fact, this container is factoring that behavior, which is already there in `PerformanceEntryObserver` (but in an ad-hoc manner and only applicable to marks/measures), with the goal of being able to do it for any performance entry type. Reviewed By: sammy-SC Differential Revision: D44544057 fbshipit-source-id: 5b7d055a6fc813ebb0fb30f9b346723161774260
245 lines
6.5 KiB
C++
245 lines
6.5 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <vector>
|
|
|
|
namespace facebook::react {
|
|
|
|
constexpr int DEFAULT_MAX_SIZE = 1024;
|
|
|
|
/**
|
|
* A container for storing entries of type T, with the following properties:
|
|
* - It can only grow up to a specified max size
|
|
* - It's a circular buffer (the oldest elements are dropped if reached max
|
|
* size and adding a new element)
|
|
* - The entries can be "consumed" (once), which from the point of view of
|
|
* the consumer effectively clears the buffer
|
|
* - Even after the entries are consumed, all of the non-overwritten entries
|
|
* can still be independently retrieved an arbitrary amount of times
|
|
*/
|
|
template <class T>
|
|
class BoundedConsumableBuffer {
|
|
public:
|
|
/**
|
|
* Status of the add/push operation for the `BoundedConsumableBuffer`
|
|
* container
|
|
*/
|
|
enum class PushStatus {
|
|
// There was free space in the buffer, element was successfully pushed:
|
|
OK = 0,
|
|
|
|
// Element was pushed, but had to overwrite some already consumed elements:
|
|
OVERWRITE = 1,
|
|
|
|
// Element wasn't pushed, as buffer size limit has been reached and it's
|
|
// not possible to overwrite already consumed elements anymore:
|
|
DROP = 2,
|
|
};
|
|
|
|
BoundedConsumableBuffer(int maxSize = DEFAULT_MAX_SIZE) : maxSize_(maxSize) {}
|
|
|
|
/**
|
|
* Adds (pushes) element into the buffer. Returns the result/status of the
|
|
* operation, which will depend on whether the buffer reached the max allowed
|
|
* size and how many are there unconsumed elements.
|
|
*/
|
|
PushStatus add(const T &&el) {
|
|
if (entries_.size() < maxSize_) {
|
|
// Haven't reached max buffer size yet, just add and grow the buffer
|
|
entries_.emplace_back(el);
|
|
cursorEnd_++;
|
|
numToConsume_++;
|
|
return PushStatus::OK;
|
|
} else if (numToConsume_ == maxSize_) {
|
|
// Drop the oldest (yet unconsumed) element in the buffer
|
|
entries_[position_] = el;
|
|
cursorEnd_ = (cursorEnd_ + 1) % maxSize_;
|
|
position_ = (position_ + 1) % maxSize_;
|
|
cursorStart_ = position_;
|
|
return PushStatus::DROP;
|
|
} else {
|
|
// Overwrite the oldest (but already consumed) element in the buffer
|
|
entries_[position_] = el;
|
|
position_ = (position_ + 1) % entries_.size();
|
|
cursorEnd_ = position_;
|
|
numToConsume_++;
|
|
return PushStatus::OVERWRITE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns pointer to next entry which would be overwritten or dropped if
|
|
* added a new element. Null if no entry will be dropped.
|
|
*/
|
|
const T *getNextOverwriteCandidate() const {
|
|
if (entries_.size() < maxSize_) {
|
|
return nullptr;
|
|
} else {
|
|
return &entries_[position_];
|
|
}
|
|
}
|
|
|
|
T &operator[](size_t idx) {
|
|
return entries_[(position_ + idx) % entries_.size()];
|
|
}
|
|
|
|
/**
|
|
* Returns reference to the last unconsumed element
|
|
*/
|
|
T &back() {
|
|
return entries_[(cursorEnd_ - 1 + entries_.size()) % entries_.size()];
|
|
}
|
|
|
|
size_t size() const {
|
|
return entries_.size();
|
|
}
|
|
|
|
size_t getNumToConsume() const {
|
|
return numToConsume_;
|
|
}
|
|
|
|
void clear() {
|
|
entries_.clear();
|
|
position_ = 0;
|
|
cursorStart_ = 0;
|
|
cursorEnd_ = 0;
|
|
numToConsume_ = 0;
|
|
}
|
|
|
|
/**
|
|
* Clears buffer entries by predicate
|
|
*/
|
|
void clear(std::function<bool(const T &)> predicate) {
|
|
int pos = cursorStart_;
|
|
std::vector<T> entries;
|
|
int numToConsume = 0;
|
|
int i;
|
|
for (i = 0; i < numToConsume_; i++) {
|
|
if (!predicate(entries_[pos])) {
|
|
entries.push_back(entries_[pos]);
|
|
numToConsume++;
|
|
}
|
|
pos = (pos + 1) % entries_.size();
|
|
}
|
|
cursorEnd_ = entries.size();
|
|
|
|
for (; i < entries_.size(); i++) {
|
|
if (!predicate(entries_[pos])) {
|
|
entries.push_back(entries_[pos]);
|
|
}
|
|
pos = (pos + 1) % entries_.size();
|
|
}
|
|
|
|
numToConsume_ = numToConsume;
|
|
cursorStart_ = 0;
|
|
cursorEnd_ = numToConsume_;
|
|
position_ = numToConsume_;
|
|
entries.swap(entries_);
|
|
}
|
|
|
|
/**
|
|
* Retrieves buffer entries, whether consumed or not
|
|
*/
|
|
std::vector<T> getEntries() const {
|
|
std::vector<T> res;
|
|
getEntries(res);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Retrieves buffer entries, whether consumed or not, with predicate
|
|
*/
|
|
std::vector<T> getEntries(std::function<bool(const T &)> predicate) const {
|
|
std::vector<T> res;
|
|
getEntries(res, predicate);
|
|
return res;
|
|
}
|
|
|
|
void getEntries(std::vector<T> &res) const {
|
|
const size_t oldSize = res.size();
|
|
res.resize(oldSize + entries_.size());
|
|
std::copy(
|
|
entries_.begin() + position_, entries_.end(), res.begin() + oldSize);
|
|
std::copy(
|
|
entries_.begin(),
|
|
entries_.begin() + position_,
|
|
res.begin() + oldSize + entries_.size() - position_);
|
|
}
|
|
|
|
void getEntries(std::vector<T> &res, std::function<bool(const T &)> predicate)
|
|
const {
|
|
for (int i = 0; i < entries_.size(); i++) {
|
|
const T &el = entries_[(i + position_) % entries_.size()];
|
|
if (predicate(el)) {
|
|
res.push_back(el);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* "Consumes" all the currently unconsumed entries in the buffer and returns
|
|
* these entries. Note that even if the buffer may not have unconsumed
|
|
* elements currently, it's still possible to retrieve all buffer elements
|
|
* via `getEntries`.
|
|
*/
|
|
std::vector<T> consume() {
|
|
std::vector<T> res;
|
|
consume(res);
|
|
return res;
|
|
}
|
|
|
|
void consume(std::vector<T> &res) {
|
|
if (numToConsume_ == 0) {
|
|
return;
|
|
}
|
|
|
|
const size_t resStart = res.size();
|
|
res.resize(res.size() + numToConsume_);
|
|
if (cursorEnd_ > cursorStart_) {
|
|
std::copy(
|
|
entries_.begin() + cursorStart_,
|
|
entries_.begin() + cursorEnd_,
|
|
res.begin() + resStart);
|
|
} else {
|
|
std::copy(
|
|
entries_.begin() + cursorStart_,
|
|
entries_.end(),
|
|
res.begin() + resStart);
|
|
std::copy(
|
|
entries_.begin(),
|
|
entries_.begin() + cursorEnd_,
|
|
res.begin() + resStart + static_cast<int>(entries_.size()) -
|
|
cursorStart_);
|
|
}
|
|
|
|
cursorStart_ = cursorEnd_;
|
|
numToConsume_ = 0;
|
|
}
|
|
|
|
private:
|
|
std::vector<T> entries_;
|
|
|
|
const int maxSize_;
|
|
|
|
// Current starting position in the circular buffer:
|
|
int position_{0};
|
|
|
|
// Current "cursor" - positions of the firsst and after last unconsumed
|
|
// element, relative to the starting position:
|
|
int cursorStart_{0};
|
|
int cursorEnd_{0};
|
|
|
|
// Number of currently unconsumed elements:
|
|
int numToConsume_{0};
|
|
};
|
|
|
|
} // namespace facebook::react
|