using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using UnityEngine; using Debug = UnityEngine.Debug; using Object = UnityEngine.Object; namespace UnityTest { [Serializable] public class AssertionComponent : MonoBehaviour, IAssertionComponentConfigurator { [SerializeField] public float checkAfterTime = 1f; [SerializeField] public bool repeatCheckTime = true; [SerializeField] public float repeatEveryTime = 1f; [SerializeField] public int checkAfterFrames = 1; [SerializeField] public bool repeatCheckFrame = true; [SerializeField] public int repeatEveryFrame = 1; [SerializeField] public bool hasFailed; [SerializeField] public CheckMethod checkMethods = CheckMethod.Start; [SerializeField] private ActionBase m_ActionBase; [SerializeField] public int checksPerformed = 0; private int m_CheckOnFrame; private string m_CreatedInFilePath = ""; private int m_CreatedInFileLine = -1; public ActionBase Action { get { return m_ActionBase; } set { m_ActionBase = value; m_ActionBase.go = gameObject; } } public Object GetFailureReferenceObject() { #if UNITY_EDITOR if (!string.IsNullOrEmpty(m_CreatedInFilePath)) { return UnityEditor.AssetDatabase.LoadAssetAtPath(m_CreatedInFilePath, typeof(Object)); } #endif return this; } public string GetCreationLocation() { if (!string.IsNullOrEmpty(m_CreatedInFilePath)) { var idx = m_CreatedInFilePath.LastIndexOf("\\") + 1; return string.Format("{0}, line {1} ({2})", m_CreatedInFilePath.Substring(idx), m_CreatedInFileLine, m_CreatedInFilePath); } return ""; } public void Awake() { if (!Debug.isDebugBuild) Destroy(this); OnComponentCopy(); } public void OnValidate() { if (Application.isEditor) OnComponentCopy(); } private void OnComponentCopy() { if (m_ActionBase == null) return; var oldActionList = Resources.FindObjectsOfTypeAll(typeof(AssertionComponent)).Where(o => ((AssertionComponent)o).m_ActionBase == m_ActionBase && o != this); // if it's not a copy but a new component don't do anything if (!oldActionList.Any()) return; if (oldActionList.Count() > 1) Debug.LogWarning("More than one refence to comparer found. This shouldn't happen"); var oldAction = oldActionList.First() as AssertionComponent; m_ActionBase = oldAction.m_ActionBase.CreateCopy(oldAction.gameObject, gameObject); } public void Start() { CheckAssertionFor(CheckMethod.Start); if (IsCheckMethodSelected(CheckMethod.AfterPeriodOfTime)) { StartCoroutine("CheckPeriodically"); } if (IsCheckMethodSelected(CheckMethod.Update)) { m_CheckOnFrame = Time.frameCount + checkAfterFrames; } } public IEnumerator CheckPeriodically() { yield return new WaitForSeconds(checkAfterTime); CheckAssertionFor(CheckMethod.AfterPeriodOfTime); while (repeatCheckTime) { yield return new WaitForSeconds(repeatEveryTime); CheckAssertionFor(CheckMethod.AfterPeriodOfTime); } } public bool ShouldCheckOnFrame() { if (Time.frameCount > m_CheckOnFrame) { if (repeatCheckFrame) m_CheckOnFrame += repeatEveryFrame; else m_CheckOnFrame = Int32.MaxValue; return true; } return false; } public void OnDisable() { CheckAssertionFor(CheckMethod.OnDisable); } public void OnEnable() { CheckAssertionFor(CheckMethod.OnEnable); } public void OnDestroy() { CheckAssertionFor(CheckMethod.OnDestroy); } public void Update() { if (IsCheckMethodSelected(CheckMethod.Update) && ShouldCheckOnFrame()) { CheckAssertionFor(CheckMethod.Update); } } public void FixedUpdate() { CheckAssertionFor(CheckMethod.FixedUpdate); } public void LateUpdate() { CheckAssertionFor(CheckMethod.LateUpdate); } public void OnControllerColliderHit() { CheckAssertionFor(CheckMethod.OnControllerColliderHit); } public void OnParticleCollision() { CheckAssertionFor(CheckMethod.OnParticleCollision); } public void OnJointBreak() { CheckAssertionFor(CheckMethod.OnJointBreak); } public void OnBecameInvisible() { CheckAssertionFor(CheckMethod.OnBecameInvisible); } public void OnBecameVisible() { CheckAssertionFor(CheckMethod.OnBecameVisible); } public void OnTriggerEnter() { CheckAssertionFor(CheckMethod.OnTriggerEnter); } public void OnTriggerExit() { CheckAssertionFor(CheckMethod.OnTriggerExit); } public void OnTriggerStay() { CheckAssertionFor(CheckMethod.OnTriggerStay); } public void OnCollisionEnter() { CheckAssertionFor(CheckMethod.OnCollisionEnter); } public void OnCollisionExit() { CheckAssertionFor(CheckMethod.OnCollisionExit); } public void OnCollisionStay() { CheckAssertionFor(CheckMethod.OnCollisionStay); } public void OnTriggerEnter2D() { CheckAssertionFor(CheckMethod.OnTriggerEnter2D); } public void OnTriggerExit2D() { CheckAssertionFor(CheckMethod.OnTriggerExit2D); } public void OnTriggerStay2D() { CheckAssertionFor(CheckMethod.OnTriggerStay2D); } public void OnCollisionEnter2D() { CheckAssertionFor(CheckMethod.OnCollisionEnter2D); } public void OnCollisionExit2D() { CheckAssertionFor(CheckMethod.OnCollisionExit2D); } public void OnCollisionStay2D() { CheckAssertionFor(CheckMethod.OnCollisionStay2D); } private void CheckAssertionFor(CheckMethod checkMethod) { if (IsCheckMethodSelected(checkMethod)) { Assertions.CheckAssertions(this); } } public bool IsCheckMethodSelected(CheckMethod method) { return method == (checkMethods & method); } #region Assertion Component create methods public static T Create(CheckMethod checkOnMethods, GameObject gameObject, string propertyPath) where T : ActionBase { IAssertionComponentConfigurator configurator; return Create(out configurator, checkOnMethods, gameObject, propertyPath); } public static T Create(out IAssertionComponentConfigurator configurator, CheckMethod checkOnMethods, GameObject gameObject, string propertyPath) where T : ActionBase { return CreateAssertionComponent(out configurator, checkOnMethods, gameObject, propertyPath); } public static T Create(CheckMethod checkOnMethods, GameObject gameObject, string propertyPath, GameObject gameObject2, string propertyPath2) where T : ComparerBase { IAssertionComponentConfigurator configurator; return Create(out configurator, checkOnMethods, gameObject, propertyPath, gameObject2, propertyPath2); } public static T Create(out IAssertionComponentConfigurator configurator, CheckMethod checkOnMethods, GameObject gameObject, string propertyPath, GameObject gameObject2, string propertyPath2) where T : ComparerBase { var comparer = CreateAssertionComponent(out configurator, checkOnMethods, gameObject, propertyPath); comparer.compareToType = ComparerBase.CompareToType.CompareToObject; comparer.other = gameObject2; comparer.otherPropertyPath = propertyPath2; return comparer; } public static T Create(CheckMethod checkOnMethods, GameObject gameObject, string propertyPath, object constValue) where T : ComparerBase { IAssertionComponentConfigurator configurator; return Create(out configurator, checkOnMethods, gameObject, propertyPath, constValue); } public static T Create(out IAssertionComponentConfigurator configurator, CheckMethod checkOnMethods, GameObject gameObject, string propertyPath, object constValue) where T : ComparerBase { var comparer = CreateAssertionComponent(out configurator, checkOnMethods, gameObject, propertyPath); if (constValue == null) { comparer.compareToType = ComparerBase.CompareToType.CompareToNull; return comparer; } comparer.compareToType = ComparerBase.CompareToType.CompareToConstantValue; comparer.ConstValue = constValue; return comparer; } private static T CreateAssertionComponent(out IAssertionComponentConfigurator configurator, CheckMethod checkOnMethods, GameObject gameObject, string propertyPath) where T : ActionBase { var ac = gameObject.AddComponent(); ac.checkMethods = checkOnMethods; var comparer = ScriptableObject.CreateInstance(); ac.Action = comparer; ac.Action.go = gameObject; ac.Action.thisPropertyPath = propertyPath; configurator = ac; #if !UNITY_METRO var stackTrace = new StackTrace(true); var thisFileName = stackTrace.GetFrame(0).GetFileName(); for (int i = 1; i < stackTrace.FrameCount; i++) { var stackFrame = stackTrace.GetFrame(i); if (stackFrame.GetFileName() != thisFileName) { string filePath = stackFrame.GetFileName().Substring(Application.dataPath.Length - "Assets".Length); ac.m_CreatedInFilePath = filePath; ac.m_CreatedInFileLine = stackFrame.GetFileLineNumber(); break; } } #endif // if !UNITY_METRO return comparer; } #endregion #region AssertionComponentConfigurator public int UpdateCheckStartOnFrame { set { checkAfterFrames = value; } } public int UpdateCheckRepeatFrequency { set { repeatEveryFrame = value; } } public bool UpdateCheckRepeat { set { repeatCheckFrame = value; } } public float TimeCheckStartAfter { set { checkAfterTime = value; } } public float TimeCheckRepeatFrequency { set { repeatEveryTime = value; } } public bool TimeCheckRepeat { set { repeatCheckTime = value; } } public AssertionComponent Component { get { return this; } } #endregion } public interface IAssertionComponentConfigurator { /// /// If the assertion is evaluated in Update, after how many frame should the evaluation start. Deafult is 1 (first frame) /// int UpdateCheckStartOnFrame { set; } /// /// If the assertion is evaluated in Update and UpdateCheckRepeat is true, how many frame should pass between evaluations /// int UpdateCheckRepeatFrequency { set; } /// /// If the assertion is evaluated in Update, should the evaluation be repeated after UpdateCheckRepeatFrequency frames /// bool UpdateCheckRepeat { set; } /// /// If the assertion is evaluated after a period of time, after how many seconds the first evaluation should be done /// float TimeCheckStartAfter { set; } /// /// If the assertion is evaluated after a period of time and TimeCheckRepeat is true, after how many seconds should the next evaluation happen /// float TimeCheckRepeatFrequency { set; } /// /// If the assertion is evaluated after a period, should the evaluation happen again after TimeCheckRepeatFrequency seconds /// bool TimeCheckRepeat { set; } AssertionComponent Component { get; } } }