using System; using System.Collections.Generic; using Rive.Utils; namespace Rive { /// /// Centralized callback pump for view model instance properties. Used by the Orchestrator /// to trigger callbacks for all subscribed properties instead of traversing the ViewModelInstance hierarchy. /// Uses weak references so it does not prevent properties from being garbage collected; /// the owning is responsible for keeping subscribed properties alive. /// internal sealed class PropertyCallbacksHub { private static PropertyCallbacksHub s_instance; internal static PropertyCallbacksHub Instance { get { if (s_instance == null) { s_instance = new PropertyCallbacksHub(); } return s_instance; } } private readonly Dictionary> m_subscribedProperties = new Dictionary>(); private readonly List m_changedScratch = new List(); private readonly List m_captureScratch = new List(); private readonly List m_deadPointersScratch = new List(); private readonly object m_lock = new object(); private PropertyCallbacksHub() { } /// /// Registers a property with the hub via a weak reference. /// The owning keeps the property alive. /// /// The property to register. internal void Register(ViewModelInstancePrimitiveProperty property) { if (property == null) { return; } IntPtr ptr = property.InstancePropertyPtr; if (ptr == IntPtr.Zero) { return; } lock (m_lock) { m_subscribedProperties[ptr] = new WeakReference(property); } } /// /// Unregisters a property from the hub. /// /// The pointer to the property to unregister. internal void Unregister(IntPtr instancePropertyPtr) { if (instancePropertyPtr == IntPtr.Zero) { return; } lock (m_lock) { m_subscribedProperties.Remove(instancePropertyPtr); } } /// /// Captures changed properties and clears their change flags. /// This should run in the same frame as panel/state machine advancement. /// /// True if any changed properties were captured. internal bool CaptureChanges() { m_changedScratch.Clear(); m_deadPointersScratch.Clear(); lock (m_lock) { m_captureScratch.Clear(); foreach (var kvp in m_subscribedProperties) { if (kvp.Value.TryGetTarget(out var property)) { m_captureScratch.Add(property); } else { m_deadPointersScratch.Add(kvp.Key); } } for (int i = 0; i < m_deadPointersScratch.Count; i++) { m_subscribedProperties.Remove(m_deadPointersScratch[i]); } } for (int i = 0; i < m_captureScratch.Count; i++) { var property = m_captureScratch[i]; if (property.HasChanged) { property.ClearChanges(); m_changedScratch.Add(property); } } return m_changedScratch.Count > 0; } /// /// Dispatches callbacks that were previously captured by . /// internal void FlushCapturedCallbacks() { for (int i = 0; i < m_changedScratch.Count; i++) { try { m_changedScratch[i].RaiseChangedEvent(); } catch (Exception e) { DebugLogger.Instance.LogException(e); } } m_changedScratch.Clear(); } #if UNITY_EDITOR // Account for Editor Domain Reload being disabled. [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)] private static void Init() { s_instance = null; } #endif } }