| #ifndef _RIVE_SIDECAR_HPP_ | |
| #define _RIVE_SIDECAR_HPP_ | |
| #include <utility> | |
| namespace rive | |
| { | |
| // 8 B owning pointer to a heap-allocated T, allocated lazily on first authored | |
| // write. Used by the generated Core base classes to hoist a cluster of rarely | |
| // authored ("cold") properties out of the inline object: the base pays only | |
| // this pointer (null) until the cluster is touched, instead of the full inline | |
| // size of every field in the cluster. | |
| // | |
| // Use when a group of properties is default on the majority of instances and is | |
| // never read on the per-frame hot path. Do NOT use for properties that are set | |
| // or read on most objects — the extra heap allocation + the null check on every | |
| // access make it strictly worse than an inline field for that case. | |
| // | |
| // All read paths are safe on a never-allocated wrapper (get() returns nullptr); | |
| // the generated getters fall back to the property's compile-time default. The | |
| // hot-path null check is a single load + branch. | |
| // | |
| // Copy/move mirror LazyVector so Core subclasses stay copyable (the Core::clone | |
| // path is default-construct + copy(), and Core deliberately keeps value | |
| // semantics). A bare std::unique_ptr member would instead delete the containing | |
| // class's implicit copy constructor. | |
| template <typename T> class Sidecar | |
| { | |
| public: | |
| Sidecar() = default; | |
| ~Sidecar() { delete m_v; } | |
| // Deep copy. Preserves the "copying the owner clones its cold state" | |
| // semantic an inline field would have provided. An empty source | |
| // (m_v == nullptr) copies as an empty wrapper — no allocation. | |
| Sidecar(const Sidecar& other) : | |
| m_v(other.m_v != nullptr ? new T(*other.m_v) : nullptr) | |
| {} | |
| Sidecar& operator=(const Sidecar& other) | |
| { | |
| if (this != &other) | |
| { | |
| delete m_v; | |
| m_v = other.m_v != nullptr ? new T(*other.m_v) : nullptr; | |
| } | |
| return *this; | |
| } | |
| Sidecar(Sidecar&& other) noexcept : m_v(other.m_v) { other.m_v = nullptr; } | |
| Sidecar& operator=(Sidecar&& other) noexcept | |
| { | |
| if (this != &other) | |
| { | |
| delete m_v; | |
| m_v = other.m_v; | |
| other.m_v = nullptr; | |
| } | |
| return *this; | |
| } | |
| // Backing object, or nullptr when never allocated. The const overload | |
| // returns a const T* so a const owner (e.g. a generated `const` getter) | |
| // can read the payload but not mutate it through the sidecar; non-const | |
| // owners get a writable T*. | |
| const T* get() const { return m_v; } | |
| T* get() { return m_v; } | |
| // Allocate on first use, then return the backing object. Generated setters | |
| // and deserialize call this before writing an authored value. | |
| T* ensureAllocated() | |
| { | |
| if (m_v == nullptr) | |
| { | |
| m_v = new T(); | |
| } | |
| return m_v; | |
| } | |
| void reset() | |
| { | |
| delete m_v; | |
| m_v = nullptr; | |
| } | |
| private: | |
| T* m_v = nullptr; | |
| }; | |
| } // namespace rive | |
| #endif |