VR4Medical/ICI/Library/PackageCache/com.unity.learn.iet-framework@4bd5247958fc/Editor/Criteria/Criterion.cs
2025-07-29 13:45:50 +03:00

227 lines
7.6 KiB
C#

#region
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;
#endregion
namespace Unity.Tutorials.Core.Editor
{
/// <summary>
/// A generic event for signaling changes in a criterion.
/// Parameters: sender.
/// </summary>
[Serializable]
public class CriterionEvent : UnityEvent<Criterion>
{
}
/// <summary>
/// Base class for Criterion implementations.
/// </summary>
public abstract class Criterion : ScriptableObject
{
/// <summary>
/// Raised when any Criterion is completed.
/// </summary>
public static CriterionEvent CriterionCompleted = new CriterionEvent();
/// <summary>
/// Raised when any Criterion is invalidated.
/// </summary>
public static CriterionEvent CriterionInvalidated = new CriterionEvent();
/// <summary>
/// Raised when this criterion is completed.
/// </summary>
[Header("Events")]
public CriterionEvent Completed = new CriterionEvent();
/// <summary>
/// Raised when this criterion is invalidated.
/// </summary>
public CriterionEvent Invalidated = new CriterionEvent();
bool m_Completed;
/// <summary>
/// Is the Criterion completed. Setting this raises CriterionCompleted/CriterionInvalidated.
/// </summary>
public bool IsCompleted
{
get { return m_Completed; }
internal set
{
if (performedAtLeastOneEvaluationSinceTestingStarted
&& (value == m_Completed))
{
return;
}
m_Completed = value;
if (m_Completed)
{
Completed?.Invoke(this);
CriterionCompleted?.Invoke(this);
}
else
{
Invalidated?.Invoke(this);
CriterionInvalidated?.Invoke(this);
}
performedAtLeastOneEvaluationSinceTestingStarted = true;
}
}
/// <summary>
/// Resets the completion state.
/// </summary>
public void ResetCompletionState()
{
IsCompleted = false;
}
/// <summary>
/// Starts testing of the criterion.
/// </summary>
public virtual void StartTesting()
{
isTesting = true;
performedAtLeastOneEvaluationSinceTestingStarted = false;
}
/// <summary>
/// Stops testing of the criterion.
/// </summary>
public virtual void StopTesting()
{
isTesting = false;
}
/// <summary>
/// Is this criterion being tested right now?
/// </summary>
[SerializeField, HideInInspector]
protected bool isTesting = false;
/// <summary>
/// Has at least one evaluation been performed since testing started?
/// </summary>
protected bool performedAtLeastOneEvaluationSinceTestingStarted = false;
/// <summary>
/// Runs update logic for the criterion.
/// </summary>
public virtual void UpdateCompletion()
{
if (!isTesting)
{
return;
}
IsCompleted = EvaluateCompletion();
}
/// <summary>
/// Evaluates if the criterion is completed.
/// </summary>
/// <returns>True if the criterion is completed, false otherwise</returns>
protected virtual bool EvaluateCompletion()
{
throw new NotImplementedException($"Missing implementation of EvaluateCompletion in: {GetType()}");
}
/// <summary>
/// Auto-completes the criterion.
/// </summary>
/// <returns>True if the auto-completion succeeded.</returns>
public abstract bool AutoComplete();
/// <summary>
/// Returns FutureObjectReference for this Criterion.
/// </summary>
/// <returns>An IEnumerable of all future objects references for this Criterion</returns>
protected virtual IEnumerable<FutureObjectReference> GetFutureObjectReferences()
{
return Enumerable.Empty<FutureObjectReference>();
}
/// <summary>
/// Destroys unreferenced future references.
/// </summary>
/// <seealso href="https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnValidate.html"/>
protected virtual void OnValidate()
{
// Find instanceIDs of referenced future references
var referencedFutureReferenceInstanceIDs = new HashSet<int>();
foreach (var futureReference in GetFutureObjectReferences())
referencedFutureReferenceInstanceIDs.Add(futureReference.GetInstanceID());
// Destroy unreferenced future references
var assetPath = AssetDatabase.GetAssetPath(this);
var assets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
foreach (var asset in assets)
{
if (asset is FutureObjectReference
&& ((FutureObjectReference)asset).Criterion == this
&& !referencedFutureReferenceInstanceIDs.Contains(asset.GetInstanceID()))
{
DestroyImmediate(asset, true);
}
}
}
/// <summary>
/// Creates a default FutureObjectReference for this Criterion.
/// </summary>
/// <returns>The new FutureObjectReference instance</returns>
protected FutureObjectReference CreateFutureObjectReference()
{
return CreateFutureObjectReference("Future Reference");
}
/// <summary>
/// Creates a FutureObjectReference by specific name for this Criterion.
/// </summary>
/// <param name="referenceName">The reference name to which the created FutureObjectReference point to</param>
/// <returns>The new FutureObjectReference instance</returns>
protected FutureObjectReference CreateFutureObjectReference(string referenceName)
{
var futureReference = CreateInstance<FutureObjectReference>();
futureReference.Criterion = this;
futureReference.ReferenceName = referenceName;
var assetPath = AssetDatabase.GetAssetPath(this);
AssetDatabase.AddObjectToAsset(futureReference, assetPath);
return futureReference;
}
/// <summary>
/// Updates names of the references.
/// </summary>
protected void UpdateFutureObjectReferenceNames()
{
// Update future reference names in next editor update due to AssetDatase interactions
EditorApplication.update += UpdateFutureObjectReferenceNamesPostponed;
}
void UpdateFutureObjectReferenceNamesPostponed()
{
// Unsubscribe immediately since it should only be called once
EditorApplication.update -= UpdateFutureObjectReferenceNamesPostponed;
var assetPath = AssetDatabase.GetAssetPath(this);
var tutorialPage = (TutorialPage)AssetDatabase.LoadMainAssetAtPath(assetPath);
var futureReferences = AssetDatabase.LoadAllAssetsAtPath(assetPath)
.Where(o => o is FutureObjectReference)
.Cast<FutureObjectReference>();
foreach (var futureReference in futureReferences)
tutorialPage.UpdateFutureObjectReferenceName(futureReference);
}
}
}