Files
BABA_YAGA/Packages/app.rive.rive-unity/Runtime/DataBinding/Helpers/PropertyCallbacksHub.cs
2026-05-19 17:39:03 +07:00

154 lines
5.1 KiB
C#

using System;
using System.Collections.Generic;
using Rive.Utils;
namespace Rive
{
/// <summary>
/// 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 <see cref="ViewModelInstance"/> is responsible for keeping subscribed properties alive.
/// </summary>
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<IntPtr, WeakReference<ViewModelInstancePrimitiveProperty>> m_subscribedProperties =
new Dictionary<IntPtr, WeakReference<ViewModelInstancePrimitiveProperty>>();
private readonly List<ViewModelInstancePrimitiveProperty> m_changedScratch = new List<ViewModelInstancePrimitiveProperty>();
private readonly List<ViewModelInstancePrimitiveProperty> m_captureScratch = new List<ViewModelInstancePrimitiveProperty>();
private readonly List<IntPtr> m_deadPointersScratch = new List<IntPtr>();
private readonly object m_lock = new object();
private PropertyCallbacksHub() { }
/// <summary>
/// Registers a property with the hub via a weak reference.
/// The owning <see cref="ViewModelInstance"/> keeps the property alive.
/// </summary>
/// <param name="property">The property to register.</param>
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<ViewModelInstancePrimitiveProperty>(property);
}
}
/// <summary>
/// Unregisters a property from the hub.
/// </summary>
/// <param name="instancePropertyPtr">The pointer to the property to unregister.</param>
internal void Unregister(IntPtr instancePropertyPtr)
{
if (instancePropertyPtr == IntPtr.Zero)
{
return;
}
lock (m_lock)
{
m_subscribedProperties.Remove(instancePropertyPtr);
}
}
/// <summary>
/// Captures changed properties and clears their change flags.
/// This should run in the same frame as panel/state machine advancement.
/// </summary>
/// <returns>True if any changed properties were captured.</returns>
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;
}
/// <summary>
/// Dispatches callbacks that were previously captured by <see cref="CaptureChanges"/>.
/// </summary>
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
}
}