blob: 34da9e63293e6dd56dbfe76c95b7fca2b14e2e40 [file]
#ifndef _RIVE_DATA_BIND_CONTAINER_HPP_
#define _RIVE_DATA_BIND_CONTAINER_HPP_
#include <cstddef>
#include <vector>
#include "rive/refcnt.hpp"
#include "rive/sidecar.hpp"
namespace rive
{
class DataContext;
class DataBind;
class Core;
// 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;
// Removed binds the container also owns; freed once the removal drains.
std::vector<DataBind*> pendingDeletes;
};
class DataBindContainer
{
public:
// Declared here and defined in the .cpp so that rcp<DataContext>'s
// ref/unref are only instantiated where DataContext is complete.
// data_context.hpp reaches back into this header through
// viewmodel_instance.hpp, so it cannot be included here, which leaves
// DataContext forward-declared for every TU that sees this class. Any
// special member left implicit — or a default member initializer on
// m_dataContext — would odr-use ~rcp right here and fail to compile in
// the TUs that never pull data_context.hpp in themselves.
DataBindContainer();
~DataBindContainer();
// Copying would alias the raw DataBind* lists and leave every bind's
// container() back-pointer aimed at the original.
DataBindContainer(const DataBindContainer&) = delete;
DataBindContainer& operator=(const DataBindContainer&) = delete;
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);
// For binds this container owns: removal now, or deferred with the
// removal when mid update so the pointer stays valid until then.
void removeAndDeleteDataBind(DataBind* dataBind);
// Drops the instance value bind clones aimed at a value about to go away.
virtual void dropInstanceValueBindsTargeting(Core* target);
// 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() {};
// The bound context swapped its main instance without a rebind.
virtual void mainViewModelInstanceChanged() {};
// Runs once an update pass has drained its deferred adds and removes.
virtual void dataBindsProcessed() {};
virtual void rebuildDataBind(DataBind*) {};
protected:
// The one data context this container is bound to. Owning: Artboard and
// StateMachineInstance create the context with make_rcp and hand it here,
// and nothing else keeps it alive. Held by the base rather than by each
// container so the pointer addDataBind() reads can never drift out of sync
// with the one the container thinks it is bound to.
const rcp<DataContext>& dataBindContext() const { return m_dataContext; }
// Sets the context WITHOUT re-pointing the existing binds. Artboard needs
// the member visible while it recurses into its artboard hosts, before the
// binds are walked; it follows this with bindDataBindsFromContext().
void dataBindContext(rcp<DataContext> dataContext);
void deleteDataBinds();
bool advanceDataBinds(float);
bool isProcessingDataBinds() const { return m_isProcessing; }
// Sets the context and re-points every DataBindContext at it.
void bindDataBindsFromContext(rcp<DataContext> dataContext);
// Re-points every DataBindContext at the already-set context.
void bindDataBindsFromContext();
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;
// No default member initializer here on purpose; see the note above.
rcp<DataContext> m_dataContext;
bool m_isProcessing = false;
};
} // namespace rive
#endif