| #ifndef _RIVE_DATA_BIND_CONTAINER_HPP_ |
| #define _RIVE_DATA_BIND_CONTAINER_HPP_ |
| #include <cstddef> |
| #include <vector> |
| #include "rive/sidecar.hpp" |
| |
| namespace rive |
| { |
| class DataContext; |
| class DataBind; |
| |
| // The data-bind work queues that most containers never touch, hoisted behind a |
| // Sidecar so DataBindContainer (a base of Artboard, StateMachineInstance, AND |
| // every DataConverter) stays small: 144 B inline becomes an 8 B pointer. |
| |
| struct DataBindQueues |
| { |
| std::vector<DataBind*> persisting; |
| // Push-driven toSource binds waiting to apply target → source. Kept |
| // separate from the dirty list so updateDataBinds can run the target→source |
| // pass *before* the source→target pass. Without this, a target→source apply |
| // triggered later in the same updateDataBinds call would see a source value |
| // updated by a sibling toTarget bind that ran first. |
| std::vector<DataBind*> dirtyToSource; |
| std::vector<DataBind*> pendingDirtyToSource; |
| std::vector<DataBind*> pendingDirty; |
| std::vector<DataBind*> pendingAdditions; |
| std::vector<DataBind*> pendingRemovals; |
| }; |
| |
| class DataBindContainer |
| { |
| public: |
| virtual void updateDataBinds(bool applyTargetToSource = true); |
| void addDataBind(DataBind* dataBind); |
| #ifdef WITH_RIVE_EDITOR |
| // Editor-only DataBind add path. Coop-hydrated DataBinds are |
| // owned by `EditorFile::m_arena`, not by this container — but |
| // they still need to participate in `updateDataBinds`, |
| // `bindDataBindsFromContext`, etc. This wraps `addDataBind` so |
| // the entry gets all the working-set plumbing |
| // (m_persistingDataBinds / m_dirtyDataBinds, container |
| // back-pointer) but gets flagged so `deleteDataBinds()` skips |
| // it at destruction. See `DataBind::isEditorOwned`. |
| void addDataBindForEditor(DataBind* dataBind); |
| // Remove every flagged (editor-owned) entry from this |
| // container's lists. Importer-added (unflagged) entries are |
| // preserved. Called by `EditorFile::finalizeBatch` at the start |
| // of every batch so the subsequent DB pass can re-add freshly |
| // and stay idempotent across batches. |
| void clearEditorDataBinds(); |
| #endif |
| void removeDataBind(DataBind* dataBind); |
| // Applies a single (source→target) data bind immediately if it is dirty. |
| // Used to refresh per-instance keyframe value holders at read time so their |
| // value is current regardless of where the batched updateDataBinds() falls |
| // in the frame. A no-op when the bind is not dirty. |
| void flushDataBind(DataBind* dataBind) { updateDataBind(dataBind, false); } |
| const std::vector<DataBind*>& dataBinds() const { return m_dataBinds; } |
| virtual void addDirtyDataBind(DataBind* dataBind); |
| virtual void rebind() {}; |
| virtual void relinkDataContext() {}; |
| virtual void rebuildDataBind(DataBind*) {}; |
| |
| protected: |
| void deleteDataBinds(); |
| bool advanceDataBinds(float); |
| void bindDataBindsFromContext(DataContext*); |
| void unbindDataBinds(); |
| void sortDataBinds(); |
| |
| private: |
| void updateDataBind(DataBind* dataBind, bool applyTargetToSource); |
| std::vector<DataBind*> m_dataBinds; |
| // The hot queue — see DataBindQueues above for why this one is inline and |
| // the other six are not. |
| std::vector<DataBind*> m_dirtyDataBinds; |
| Sidecar<DataBindQueues> m_queues; |
| DataContext* m_dataContext = nullptr; |
| bool m_isProcessing = false; |
| }; |
| } // namespace rive |
| |
| #endif |