blob: 190917b5f72274287134f1395132254503c11f73 [file]
#ifndef _RIVE_CORE_HPP_
#define _RIVE_CORE_HPP_
#include "rive/rive_types.hpp"
#include "rive/core/binary_reader.hpp"
#include "rive/status_code.hpp"
#ifdef DEBUG
#define DEBUG_PRINT(msg) fprintf(stderr, msg "\n");
#else
#define DEBUG_PRINT(msg)
#endif
namespace rive
{
class CoreContext;
class DataBind;
class ImportStack;
class Core
{
public:
Core() = default;
// m_firstObserver is intrusive — observers cache `this` as their target
// and chain through `m_nextObserver` rooted at this Core. Cloning a Core
// must NOT inherit the original's observer list head: the new instance
// has no observers of its own, and propagating the head pointer would
// cause notifyPropertyChanged to dirty binds that point at the original,
// and ~Core() on the clone to wrongly detach them.
Core(const Core&) : m_firstObserver(nullptr) {}
Core& operator=(const Core&)
{
// Intentional no-op for the observer list: our existing observers
// continue to point at us; theirs continue to point at them. Any
// other state that's safe to copy lives in derived classes.
return *this;
}
// Move ops follow the same rule: the observer chain is bound to the
// original Core's address, so moving cannot transfer it. Default-init
// the new instance's head; the moved-from instance keeps its observers
// (and the responsibility to detach them at destruction).
Core(Core&&) noexcept : m_firstObserver(nullptr) {}
Core& operator=(Core&&) noexcept { return *this; }
const uint32_t emptyId = -1;
static const int invalidPropertyKey = 0;
virtual ~Core();
virtual uint16_t coreType() const = 0;
virtual bool isTypeOf(uint16_t typeKey) const = 0;
virtual bool deserialize(uint16_t propertyKey, BinaryReader& reader) = 0;
template <typename T> inline bool is() const
{
return isTypeOf(T::typeKey);
}
template <typename T> inline T* as()
{
assert(is<T>());
return static_cast<T*>(this);
}
/// Make a shallow copy of the object.
virtual Core* clone() const { return nullptr; }
template <typename T> inline const T* as() const
{
assert(is<T>());
return static_cast<const T*>(this);
}
/// Called to validate the object can be used at runtime.
virtual bool validate(CoreContext* context) { return true; }
/// Called when the object is first added to the context, other objects
/// may not have resolved their dependencies yet. This is an opportunity
/// to look up objects referenced by id, but not assume that they in
/// turn have resolved their references yet. Called during
/// load/instance.
virtual StatusCode onAddedDirty(CoreContext* context)
{
return StatusCode::Ok;
}
/// Called when all the objects in the context have had onAddedDirty
/// called. This is an opportunity to reference things referenced by
/// dependencies. (A path should be able to find a Shape somewhere in
/// its hierarchy, which may be multiple levels up).
virtual StatusCode onAddedClean(CoreContext* context)
{
return StatusCode::Ok;
}
virtual StatusCode import(ImportStack& importStack)
{
return StatusCode::Ok;
}
// Push-notification hook for target→source data binds. Property setters
// generated in *_base.hpp call this with the affected property key after a
// value changes. Default impl walks the intrusive observer list and
// dirties any DataBind that subscribed for `propertyKey` on this Core.
//
// Non-virtual: every Core inherits the same dispatch. The hot path
// (no observers) is a single null-pointer test.
void notifyPropertyChanged(uint16_t propertyKey);
// Intrusive subscriber list management. Used by DataBind to subscribe to
// target value changes without paying a per-Core map allocation. Each
// Core holds the list head; each subscribing DataBind holds m_nextObserver
// for the same (target, key) chain. Observer order is not guaranteed.
void addPropertyObserver(DataBind* observer);
void removePropertyObserver(DataBind* observer);
private:
DataBind* m_firstObserver = nullptr;
};
} // namespace rive
#endif