| #ifndef _RIVE_LAZY_VECTOR_HPP_ |
| #define _RIVE_LAZY_VECTOR_HPP_ |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <utility> |
| #include <vector> |
| |
| namespace rive |
| { |
| |
| // 8 B owning pointer to a heap-allocated std::vector<T>, allocated on first |
| // mutation. Saves 16 B per object compared to an inline std::vector<T> (24 B |
| // header) for fields that are empty most of the time. |
| // |
| // Use when you have a per-object list that's empty for the majority of |
| // instances (typical fits: dependents/observers/optional adornments). Do NOT |
| // use when the vector is usually populated — the extra heap allocation makes |
| // it strictly worse than a regular vector for that case. |
| // |
| // All read operations are safe on an empty (never-allocated) wrapper. The |
| // hot-path null check is a single load + branch; reads return iterators into |
| // a shared static empty vector when the wrapper has no backing storage. |
| template <typename T> class LazyVector |
| { |
| public: |
| using value_type = T; |
| using const_iterator = typename std::vector<T>::const_iterator; |
| |
| LazyVector() = default; |
| ~LazyVector() { delete m_v; } |
| |
| // Deep copy. Preserves the "copying the owner clones its list" semantic |
| // that a plain std::vector member would have provided. An empty source |
| // (m_v == nullptr) copies as an empty wrapper — no allocation. This |
| // keeps Component / Drawable / etc. copyable (Core::clone path). |
| LazyVector(const LazyVector& other) : |
| m_v(other.m_v != nullptr ? new std::vector<T>(*other.m_v) : nullptr) |
| {} |
| LazyVector& operator=(const LazyVector& other) |
| { |
| if (this != &other) |
| { |
| delete m_v; |
| m_v = |
| other.m_v != nullptr ? new std::vector<T>(*other.m_v) : nullptr; |
| } |
| return *this; |
| } |
| |
| LazyVector(LazyVector&& other) noexcept : m_v(other.m_v) |
| { |
| other.m_v = nullptr; |
| } |
| LazyVector& operator=(LazyVector&& other) noexcept |
| { |
| if (this != &other) |
| { |
| delete m_v; |
| m_v = other.m_v; |
| other.m_v = nullptr; |
| } |
| return *this; |
| } |
| |
| bool empty() const { return m_v == nullptr || m_v->empty(); } |
| std::size_t size() const { return m_v == nullptr ? 0 : m_v->size(); } |
| |
| // Append unconditionally. Allows duplicates (use pushUnique to dedup). |
| void push_back(T value) |
| { |
| if (m_v == nullptr) |
| { |
| m_v = new std::vector<T>(); |
| } |
| m_v->push_back(std::move(value)); |
| } |
| |
| // Append only if `value` is not already present. Centralizes the |
| // find-then-push idiom used by callers that maintain set semantics. |
| void pushUnique(T value) |
| { |
| if (m_v != nullptr && |
| std::find(m_v->begin(), m_v->end(), value) != m_v->end()) |
| { |
| return; |
| } |
| push_back(std::move(value)); |
| } |
| |
| // Erase every element equal to `value`. No-op when wrapper is empty. |
| // Uses the erase-remove idiom — safe but removes ALL duplicates if any. |
| void eraseAll(const T& value) |
| { |
| if (m_v == nullptr) |
| { |
| return; |
| } |
| m_v->erase(std::remove(m_v->begin(), m_v->end(), value), m_v->end()); |
| } |
| |
| void clear() |
| { |
| if (m_v != nullptr) |
| { |
| m_v->clear(); |
| } |
| } |
| |
| // Const iteration always works, even on a never-allocated wrapper — |
| // returns iterators into a shared static empty vector when m_v is null. |
| // Inlined null check (no view() indirection) so the compiler can keep |
| // m_v in a register across both begin/end loads in a range-for. |
| const_iterator begin() const |
| { |
| return m_v ? m_v->begin() : emptyView().begin(); |
| } |
| const_iterator end() const { return m_v ? m_v->end() : emptyView().end(); } |
| |
| // Returns a stable const reference suitable as the return value of |
| // accessor methods. Always valid — falls back to a shared empty when |
| // the wrapper has no backing storage. |
| const std::vector<T>& view() const { return m_v ? *m_v : emptyView(); } |
| |
| private: |
| std::vector<T>* m_v = nullptr; |
| |
| static const std::vector<T>& emptyView() |
| { |
| static const std::vector<T> EMPTY; |
| return EMPTY; |
| } |
| }; |
| |
| } // namespace rive |
| #endif |