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
}
}