//#define DEBUG_LOG_NAME using UnityEngine; using System.Collections.Generic; namespace Unity.Cinemachine { /// /// Attempt to track on what clock transforms get updated /// internal class UpdateTracker { public enum UpdateClock { Fixed = 1, Late = 2} class UpdateStatus { const int kWindowSize = 30; int m_WindowStart; int m_NumWindowLateUpdateMoves; int m_NumWindowFixedUpdateMoves; int m_NumWindows; int m_LastFrameUpdated; Matrix4x4 m_LastPos; #if DEBUG_LOG_NAME string m_Name; #endif public UpdateClock PreferredUpdate { get; private set; } #if DEBUG_LOG_NAME public UpdateStatus(string targetName, int currentFrame, Matrix4x4 pos) { m_Name = targetName; #else public UpdateStatus(int currentFrame, Matrix4x4 pos) { #endif m_WindowStart = currentFrame; m_LastFrameUpdated = Time.frameCount; PreferredUpdate = UpdateClock.Late; m_LastPos = pos; } public void OnUpdate(int currentFrame, UpdateClock currentClock, Matrix4x4 pos) { if (m_LastPos == pos) return; if (currentClock == UpdateClock.Late) ++m_NumWindowLateUpdateMoves; else if (m_LastFrameUpdated != currentFrame) // only count 1 per rendered frame ++m_NumWindowFixedUpdateMoves; m_LastPos = pos; UpdateClock choice = UpdateClock.Late; if (m_NumWindowFixedUpdateMoves > 3 && m_NumWindowLateUpdateMoves < m_NumWindowFixedUpdateMoves / 3) choice = UpdateClock.Fixed; if (m_NumWindows == 0) PreferredUpdate = choice; if (m_WindowStart + kWindowSize <= currentFrame) { #if DEBUG_LOG_NAME Debug.Log(m_Name + ": Window " + m_NumWindows + ": Late=" + m_NumWindowLateUpdateMoves + ", Fixed=" + m_NumWindowFixedUpdateMoves + ", currentClock=" + currentClock); #endif PreferredUpdate = choice; ++m_NumWindows; m_WindowStart = currentFrame; m_NumWindowLateUpdateMoves = (PreferredUpdate == UpdateClock.Late) ? 1 : 0; m_NumWindowFixedUpdateMoves = (PreferredUpdate == UpdateClock.Fixed) ? 1 : 0; } } } static Dictionary s_UpdateStatus = new(); [RuntimeInitializeOnLoadMethod] static void InitializeModule() => s_UpdateStatus.Clear(); static List s_ToDelete = new(); static void UpdateTargets(UpdateClock currentClock) { // Update the registry for all known targets int now = Time.frameCount; var iter = s_UpdateStatus.GetEnumerator(); while (iter.MoveNext()) { var current = iter.Current; if (current.Key == null) s_ToDelete.Add(current.Key); // target was deleted else current.Value.OnUpdate(now, currentClock, current.Key.localToWorldMatrix); } for (int i = s_ToDelete.Count-1; i >= 0; --i) s_UpdateStatus.Remove(s_ToDelete[i]); s_ToDelete.Clear(); iter.Dispose(); } public static UpdateClock GetPreferredUpdate(Transform target) { if (Application.isPlaying && target != null) { if (s_UpdateStatus.TryGetValue(target, out var status)) return status.PreferredUpdate; // Add the target to the registry #if DEBUG_LOG_NAME status = new UpdateStatus(target.name, Time.frameCount, target.localToWorldMatrix); #else status = new UpdateStatus(Time.frameCount, target.localToWorldMatrix); #endif s_UpdateStatus.Add(target, status); } return UpdateClock.Late; } static object s_LastUpdateContext; public static void OnUpdate(UpdateClock currentClock, object context) { // Do something only if we are the first controller processing this frame if (s_LastUpdateContext == null || s_LastUpdateContext == context) { s_LastUpdateContext = context; UpdateTargets(currentClock); } } public static void ForgetContext(object context) { if (s_LastUpdateContext == context) s_LastUpdateContext = null; } } }