// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // #if UNITY_EDITOR using System.Collections.Generic; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; namespace Animancer.Editor { /// [Editor-Only] /// Keeps track of instances /// to ensure that they're properly cleaned up. /// /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerGraphCleanup public static class AnimancerGraphCleanup { /************************************************************************************************************************/ private static List _AllGraphs; /// [Editor-Only] Registers a `graph` to make sure it gets cleaned up properly. public static void AddGraph(AnimancerGraph graph) { if (_AllGraphs == null) { _AllGraphs = new(); AssemblyReloadEvents.beforeAssemblyReload += () => DestroyAll(EditorApplication.isPlaying); EditorApplication.playModeStateChanged += change => { switch (change) { case PlayModeStateChange.EnteredEditMode: DestroyAll(true); break; case PlayModeStateChange.ExitingEditMode: DestroyAll(false); break; } }; } else { EditModeDestroyOldInstances(); } _AllGraphs.Add(graph); } /// [Editor-Only] Removes the `graph` from the list of instances. public static void RemoveGraph(AnimancerGraph graph) { _AllGraphs?.Remove(graph); AnimancerGraph.ClearInactiveInitializationStackTrace(graph); } /************************************************************************************************************************/ private static void DestroyAll(bool isPlaying) { for (int i = _AllGraphs.Count - 1; i >= 0; i--) { var graph = _AllGraphs[i]; if (graph.IsValidOrDispose()) { if (isPlaying && graph.InactiveInitializationStackTrace != null) { Debug.LogWarning( $"{graph} was not properly destroyed." + $" Its {nameof(GameObject)} was inactive and never activated," + $" meaning that Unity didn't call its AnimancerComponent.OnDestroy." + $"\n\nIf you need to use Animancer on an object that never gets activated," + $" you must call animancerComponent.Graph.Destroy() on it manually." + $"\n\nThis graph was created:\n{graph.InactiveInitializationStackTrace}\n", graph.Component as Object); AnimancerGraph.ClearInactiveInitializationStackTrace(graph); } graph.Destroy(); } } _AllGraphs.Clear(); } /************************************************************************************************************************/ private static void EditModeDestroyOldInstances() { if (EditorApplication.isPlayingOrWillChangePlaymode) return; for (int i = _AllGraphs.Count - 1; i >= 0; i--) { var graph = _AllGraphs[i]; if (!ShouldStayAlive(graph)) { if (graph.IsValidOrDispose()) graph.Destroy();// This will remove it. else _AllGraphs.RemoveAt(i); } } } /************************************************************************************************************************/ /// Should this graph should stay alive instead of being destroyed? private static bool ShouldStayAlive(AnimancerGraph graph) { if (!graph.IsValidOrDispose()) return false; if (graph.Component == null) return true; if (graph.Component is Object obj && obj == null) return false; if (graph.Component.Animator == null) return false; return true; } /************************************************************************************************************************/ /// [Editor-Only] /// Returns true if the `initial` mode was /// and the `current` has changed to another mode or if the `initial` mode was something else /// and the `current` has changed to . /// public static bool HasChangedToOrFromAnimatePhysics(AnimatorUpdateMode? initial, AnimatorUpdateMode current) { if (initial == null) return false; #if UNITY_2023_1_OR_NEWER var wasAnimatePhysics = initial.Value == AnimatorUpdateMode.Fixed; var isAnimatePhysics = current == AnimatorUpdateMode.Fixed; #else var wasAnimatePhysics = initial.Value == AnimatorUpdateMode.AnimatePhysics; var isAnimatePhysics = current == AnimatorUpdateMode.AnimatePhysics; #endif return wasAnimatePhysics != isAnimatePhysics; } /************************************************************************************************************************/ } } namespace Animancer { /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerGraph public partial class AnimancerGraph { /************************************************************************************************************************/ /// [Editor-Only] [Internal] /// A stack trace captured in /// if the is false. /// /// /// This is used to warn if the graph isn't destroyed /// because Unity won't call OnDestroy if the object is never activated. /// internal System.Diagnostics.StackTrace InactiveInitializationStackTrace { get; private set; } /************************************************************************************************************************/ /// [Editor-Only] Captures the . private void CaptureInactiveInitializationStackTrace(IAnimancerComponent animancer) { if (!animancer.gameObject.activeInHierarchy && EditorApplication.isPlayingOrWillChangePlaymode) InactiveInitializationStackTrace = new(1, true); } /************************************************************************************************************************/ /// [Editor-Only] [Internal] Discards the . public static void ClearInactiveInitializationStackTrace(AnimancerGraph graph) { graph.InactiveInitializationStackTrace = null; } /************************************************************************************************************************/ } } #endif