using System; using System.Runtime.InteropServices; using Rive.Utils; namespace Rive { /// /// Base class for all primitive properties of a ViewModelInstance. This is usually used for types like numbers, strings, etc. that have a value change event. /// public abstract class ViewModelInstancePrimitiveProperty : ViewModelInstanceProperty { private IntPtr m_instanceValuePropertyPtr; private ViewModelInstance m_instance; // The instance this property belongs to internal IntPtr InstancePropertyPtr => m_instanceValuePropertyPtr; /// /// The instance this property belongs to. /// internal ViewModelInstance RootInstance => m_instance; /// /// Throws if the owning has been disposed. /// protected void ThrowIfOwnerDisposed() { if (m_instance != null && m_instance.IsDisposed) { throw new ObjectDisposedException( nameof(ViewModelInstance), $"Cannot access {GetType().Name}: the owning ViewModelInstance has been disposed."); } } /// /// Whether the value has changed since the last time it was read. /// internal bool HasChanged => viewModelInstancePropertyValueHasChanged(m_instanceValuePropertyPtr); /// /// Constructor for a ViewModelInstanceProperty. /// /// Pointer to the instance property value. /// The instance this property belongs to. internal ViewModelInstancePrimitiveProperty(IntPtr instanceValuePtr, ViewModelInstance instance) { m_instanceValuePropertyPtr = instanceValuePtr; m_instance = instance; } ~ViewModelInstancePrimitiveProperty() { if (m_instanceValuePropertyPtr != IntPtr.Zero) { ViewModelInstanceProperty.RemoveCachedPropertyForPointer(m_instanceValuePropertyPtr); m_instanceValuePropertyPtr = IntPtr.Zero; } } /// /// Reset the changed flag for this value. /// internal void ClearChanges() { clearViewModelInstancePropertyValueChanges(m_instanceValuePropertyPtr); } /// /// Called by ViewModelInstance when it detects this property has changed. /// internal virtual void RaiseChangedEvent() { // no-op in non-generic base; override in subclasses } /// /// Clears this property's callbacks and performs the normal cleanup path. /// Use this when the property is unsubscribing itself, because it also tells /// the owning to unregister the property. /// internal virtual void ClearAllCallbacks() { // If we've removed all subscribers, unregister m_instance?.UnregisterPropertyForCallbacks(this); } /// /// Clears only this property's stored callback delegates. /// Unlike , this does not unregister the property /// from its owning . /// This is used by because that /// method is already walking the subscribed properties and handling hub cleanup /// itself. Calling the full unregister path there would change the collection /// while it is being looped over. /// internal virtual void ClearDelegatesOnly() { } internal void RegisterForCallbacks() { // Since we don't clean the changed flag for properties that don't have listeners, // we clean it the first time we add a listener to it ClearChanges(); m_instance.RegisterPropertyForCallbacks(this); } internal void UnregisterForCallbacks() { m_instance.UnregisterPropertyForCallbacks(this); } [DllImport(NativeLibrary.name)] private static extern bool viewModelInstancePropertyValueHasChanged(IntPtr instanceValue); [DllImport(NativeLibrary.name)] private static extern void clearViewModelInstancePropertyValueChanges(IntPtr instanceValue); // Helpers to add/remove managed callbacks and register/unregister native notifications /// /// Adds a callback handler and, if first subscriber, clears stale changes and registers native callbacks. /// /// The callback to invoke when this property changes. /// Reference to the private delegate field storing subscribers. protected void AddPropertyCallback(Action handler, ref Action backingField) { ThrowIfOwnerDisposed(); bool wasEmpty = backingField == null; backingField += handler; if (wasEmpty) { ClearChanges(); RegisterForCallbacks(); } } /// /// Removes a callback handler and, if no subscribers remain, unregisters native callbacks. /// /// The callback to remove. /// Reference to the private delegate field storing subscribers. protected void RemovePropertyCallback(Action handler, ref Action backingField) { backingField -= handler; if (backingField == null) { UnregisterForCallbacks(); } } /// /// Adds a typed callback handler and, if first subscriber, clears changes and registers native callbacks. /// protected void AddPropertyCallback(Action handler, ref Action backingField) { ThrowIfOwnerDisposed(); bool wasEmpty = backingField == null; backingField += handler; if (wasEmpty) { ClearChanges(); RegisterForCallbacks(); } } /// /// Removes a typed callback handler and unregisters native callbacks if no subscribers remain. /// protected void RemovePropertyCallback(Action handler, ref Action backingField) { backingField -= handler; if (backingField == null) { UnregisterForCallbacks(); } } } /// /// Generic subclass of ViewModelInstancePrimitiveProperty for primitive types. This class allows you to register a callback for when the value changes and provides a typed Value property. /// /// The type of the value. /// This class is used for primitive types like strings, numbers, etc. public abstract class ViewModelInstancePrimitiveProperty : ViewModelInstancePrimitiveProperty { /// /// Event raised when the value changes, passing the new value. /// public event Action OnValueChanged { add => AddPropertyCallback(value, ref m_onValueChanged); remove => RemovePropertyCallback(value, ref m_onValueChanged); } private Action m_onValueChanged; internal ViewModelInstancePrimitiveProperty(IntPtr instanceValuePtr, ViewModelInstance instance) : base(instanceValuePtr, instance) { } internal override void RaiseChangedEvent() { m_onValueChanged?.Invoke(Value); } internal override void ClearAllCallbacks() { m_onValueChanged = null; base.ClearAllCallbacks(); } internal override void ClearDelegatesOnly() { m_onValueChanged = null; } public abstract T Value { get; set; } } }