using System; using System.Collections; using System.Collections.Generic; using Rive.Utils; using UnityEngine; namespace Rive.Components { /// /// Central per-frame orchestrator for ticking panels/widgets and then processing databinding callbacks. /// /// This enables "command-server-like" batching: /// - panels register/unregister with the orchestrator /// - on each frame, auto panels are ticked /// - manual ticks execute immediately and notify the orchestrator /// - changed properties are triggered after all panels have been ticked. /// internal sealed class Orchestrator : MonoBehaviour { private static bool s_isDestroyed = false; private static Orchestrator s_instance; private static readonly List s_registeredRenderTargetStrategies = new List(); public static Orchestrator Instance { get { if (s_isDestroyed) { return null; } if (s_instance != null) { return s_instance; } // Ensure the render pipeline handler exists; the orchestrator will be attached there. var handler = RenderPipelineHelper.GetOrCreateHandler() as MonoBehaviour; if (handler == null) { return null; } s_instance = handler.GetComponent(); if (s_instance == null) { s_instance = handler.gameObject.AddComponent(); } return s_instance; } } private readonly HashSet m_registeredPanels = new HashSet(); private readonly List m_panelTickList = new List(); // Used to store the panels to tick in the TickAutoPanels method to avoid modifying the HashSet while iterating. private bool m_tickedThisFrame; internal event Action OnPostRenderPreparation; private void OnDestroy() { m_tickedThisFrame = false; if (s_instance == this && !s_isDestroyed) { s_instance = null; s_isDestroyed = true; } } internal void RegisterPanel(RivePanel panel) { if (panel == null) { return; } m_registeredPanels.Add(panel); } internal void UnregisterPanel(RivePanel panel) { if (panel == null) { return; } m_registeredPanels.Remove(panel); } internal void NotifyManualTickOccurred(RivePanel panel) { if (panel == null) { return; } m_tickedThisFrame = true; } internal static void RegisterRenderTargetStrategy(RenderTargetStrategy strategy) { if (strategy == null) { return; } // Avoid duplicates. for (int i = 0; i < s_registeredRenderTargetStrategies.Count; i++) { if (ReferenceEquals(s_registeredRenderTargetStrategies[i], strategy)) { return; } } s_registeredRenderTargetStrategies.Add(strategy); } internal static void UnregisterRenderTargetStrategy(RenderTargetStrategy strategy) { if (strategy == null) { return; } // We set to null rather than remove to avoid shifting indices during reverse iteration. for (int i = s_registeredRenderTargetStrategies.Count - 1; i >= 0; i--) { if (ReferenceEquals(s_registeredRenderTargetStrategies[i], strategy)) { s_registeredRenderTargetStrategies[i] = null; return; } } } private static void PrepareRenderTargetsStrategies() { if (s_registeredRenderTargetStrategies.Count == 0) { return; } // We iterate in reverse so removals are safe . for (int i = s_registeredRenderTargetStrategies.Count - 1; i >= 0; i--) { var strategy = s_registeredRenderTargetStrategies[i]; // Accounting for Unity "fake null" when the strategy is destroyed. if (strategy == null) { s_registeredRenderTargetStrategies.RemoveAt(i); continue; } strategy.PrepareRenderFromOrchestrator(); } } private bool TickAutoPanels() { // Panels with Auto update mode are ticked here. if (m_registeredPanels.Count == 0) { return false; } float deltaTime = Time.deltaTime; bool tickedAny = false; m_panelTickList.Clear(); m_panelTickList.AddRange(m_registeredPanels); for (int i = 0; i < m_panelTickList.Count; i++) { var panel = m_panelTickList[i]; if (panel == null || !panel.isActiveAndEnabled) { continue; } if (panel.UpdateMode != RivePanel.PanelUpdateMode.Auto) { continue; } tickedAny = true; try { panel.TickImmediate(deltaTime); } catch (System.Exception e) { DebugLogger.Instance.LogException(e); } } m_panelTickList.Clear(); return tickedAny; } private void Update() { // We advance any Rive Panels set to Auto update mode. Manual panels notify the orchestrator when they tick via NotifyManualTickOccurred. if (TickAutoPanels()) { m_tickedThisFrame = true; } TriggerCallbacksForChangedProperties(); } /// /// Captures changed properties and triggers the Unity callbacks. This only triggers/checks for properties the user has subscribed to in code. /// private void TriggerCallbacksForChangedProperties() { if (RiveWidget.PropertyCallbackApproach == RiveWidget.DataBindingPropertyCallbackApproach.Orchestrator && m_tickedThisFrame) { if (PropertyCallbacksHub.Instance.CaptureChanges()) { PropertyCallbacksHub.Instance.FlushCapturedCallbacks(); // This triggers the Unity callbacks. } } m_tickedThisFrame = false; } private void LateUpdate() { // Prepare batched rendering after ticking panels. // This is intentionally called every frame so batched render requests (e.g. from // registration/size/layout changes) can be handled even when no panels ticked. PrepareRenderTargetsStrategies(); OnPostRenderPreparation?.Invoke(); } #if UNITY_EDITOR // We account for Domain Reload in the editor being disabled [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void Init() { s_instance = null; s_isDestroyed = false; s_registeredRenderTargetStrategies.Clear(); } #endif } }