123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- using System;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Playables;
- namespace Animancer
- {
- /// <summary>
- /// Enforces various rules throughout the system, most of which are compiled out if UNITY_ASSERTIONS is not defined
- /// (by default, it is only defined in the Unity Editor and in Development Builds).
- /// </summary>
- /// https://kybernetik.com.au/animancer/api/Animancer/Validate
- ///
- public static partial class Validate
- {
- /************************************************************************************************************************/
- /// <summary>[Assert-Conditional]
- /// Throws if the `clip` is <c>null</c>, not an asset, or marked as <see cref="AnimationClip.legacy"/>.
- /// </summary>
- /// <exception cref="NullReferenceException"/>
- /// <exception cref="ArgumentException"/>
- [System.Diagnostics.Conditional(Strings.Assertions)]
- public static void AssertAnimationClip(AnimationClip clip, bool throwIfNull, string operation)
- {
- #if UNITY_ASSERTIONS
- if (clip == null)
- {
- if (!throwIfNull)
- return;
- #pragma warning disable IDE0041 // Use 'is null' check (that would suggest changing to == which is wrong).
- var error = ReferenceEquals(clip, null)
- ? $"Unable to {operation} because the {nameof(AnimationClip)} is null."
- : $"Unable to {operation} because the {nameof(AnimationClip)} has been destroyed.";
- throw new NullReferenceException(error);
- #pragma warning restore IDE0041 // Use 'is null' check.
- }
- #if UNITY_EDITOR
- if (OptionalWarning.DynamicAnimation.IsEnabled() &&
- !UnityEditor.EditorUtility.IsPersistent(clip))
- OptionalWarning.DynamicAnimation.Log(
- $"Attempted to {operation} using an {nameof(AnimationClip)} '{clip.name}' which is not an asset." +
- " Unity doesn't suppport dynamically creating animations for Animancer in runtime builds." +
- " This warning should be disabled if you only intend to use the animation in the" +
- " Unity Editor and not create it in a runtime build.",
- clip);
- #endif
- if (clip.legacy)
- throw new ArgumentException(
- $"Unable to {operation} because the {nameof(AnimationClip)} '{clip.name}' is a lagacy animation" +
- " and therefore cannot be used by Animancer" +
- " If it was imported as part of a model then the model's Rig type must be Humanoid or Generic." +
- " Otherwise you can use the 'Toggle Legacy' function in the clip's context menu" +
- " (via the cog icon in the top right of its Inspector).");
- #endif
- }
- /************************************************************************************************************************/
- /// <summary>[Assert-Conditional] Throws if the <see cref="AnimancerNodeBase.Graph"/> is not the `graph`.</summary>
- /// <exception cref="ArgumentException"/>
- [System.Diagnostics.Conditional(Strings.Assertions)]
- public static void AssertGraph(AnimancerNode node, AnimancerGraph graph)
- {
- #if UNITY_ASSERTIONS
- if (node.Graph != graph)
- {
- AnimancerNodeBase.MarkAsUsed(node);
- throw new ArgumentException(
- $"{nameof(AnimancerNode)}.{nameof(AnimancerNode.Graph)} mismatch:" +
- $" cannot use a node in an {nameof(AnimancerGraph)} that is not its {nameof(AnimancerNode.Graph)}: " +
- node.GetDescription());
- }
- #endif
- }
- /************************************************************************************************************************/
- /// <summary>[Assert-Conditional] Throws if the `node`'s <see cref="Playable"/> is invalid.</summary>
- /// <exception cref="InvalidOperationException"/>
- [System.Diagnostics.Conditional(Strings.Assertions)]
- public static void AssertPlayable(AnimancerNode node)
- {
- #if UNITY_ASSERTIONS
- if (node._Playable.IsValid() &&
- node.Graph._PlayableGraph.IsValid())
- return;
- var description = node.ToString();
- var stackTrace = AnimancerNode.GetConstructorStackTrace(node);
- if (stackTrace != null)
- description += "\n\n" + stackTrace;
- AnimancerNodeBase.MarkAsUsed(node);
- if (node is AnimancerState state)
- state.Destroy();
- if (node.Graph == null)
- throw new InvalidOperationException(
- $"{nameof(AnimancerNode)}.{nameof(AnimancerNode.Graph)} hasn't been set so its" +
- $" {nameof(Playable)} hasn't been created. It can be set by playing the state" +
- $" or calling {nameof(AnimancerState.SetGraph)} on it directly." +
- $" {nameof(AnimancerState.SetParent)} would also work if the parent has a" +
- $" {nameof(AnimancerNode.Graph)}." +
- $"\n• Node: {description}");
- else if (!node.Graph._PlayableGraph.IsValid())
- throw new InvalidOperationException(
- $"{nameof(AnimancerGraph)}.{nameof(AnimancerGraph.PlayableGraph)} has already been destroyed." +
- $" This is often caused by a character attempting to access a state on a different character," +
- $" such as if they share a Transition and are both accessing its State without realising it" +
- $" only holds the most recently played state." +
- $"\n• Graph: {node.Graph}" +
- $"\n• Node: {description}");
- else
- throw new InvalidOperationException(
- $"{nameof(AnimancerNode)}.{nameof(AnimancerNodeBase.Playable)}" +
- $" has either been destroyed or was never created." +
- $"\n• Graph: {node.Graph}" +
- $"\n• Node: {description}");
- #endif
- }
- /************************************************************************************************************************/
- /// <summary>[Assert-Conditional]
- /// Throws if the `state` was not actually assigned to its specified <see cref="AnimancerNode.Index"/> in
- /// the `states`.
- /// </summary>
- /// <exception cref="InvalidOperationException"/>
- /// <exception cref="IndexOutOfRangeException">
- /// The <see cref="AnimancerNode.Index"/> is larger than the number of `states`.
- /// </exception>
- [System.Diagnostics.Conditional(Strings.Assertions)]
- public static void AssertCanRemoveChild(AnimancerState state, IList<AnimancerState> childStates, int childCount)
- {
- #if UNITY_ASSERTIONS
- var index = state.Index;
- if (index < 0)
- throw new InvalidOperationException(
- $"Cannot remove a child state that did not have an {nameof(state.Index)} assigned");
- if ((uint)index >= (uint)childCount)
- throw new IndexOutOfRangeException(
- $"{nameof(AnimancerState)}.{nameof(state.Index)} ({index})" +
- $" is outside the collection of states (Count {childCount})");
- if (childStates[index] != state)
- throw new InvalidOperationException(
- $"Cannot remove a child state that was not actually connected to its port on {state.Parent}:" +
- $"\n• Port: {index}" +
- $"\n• Connected Child: {AnimancerUtilities.ToStringOrNull(childStates[index])}" +
- $"\n• Disconnecting Child: {AnimancerUtilities.ToStringOrNull(state)}");
- #endif
- }
- /************************************************************************************************************************/
- /// <summary>[Assert-Conditional] Throws if the `weight` is negative, infinity, or NaN.</summary>
- /// <exception cref="ArgumentOutOfRangeException"/>
- [System.Diagnostics.Conditional(Strings.Assertions)]
- public static void AssertSetWeight(AnimancerNode node, float weight)
- {
- #if UNITY_ASSERTIONS
- if (!(weight >= 0) || weight == float.PositiveInfinity)// Reversed comparison includes NaN.
- {
- AnimancerNodeBase.MarkAsUsed(node);
- throw new ArgumentOutOfRangeException(
- nameof(weight),
- weight,
- $"{nameof(AnimancerNode.Weight)} must be a finite positive value");
- }
- #endif
- }
- /************************************************************************************************************************/
- }
- }
|