VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Runtime/Utilities/Tweenables/TweenableVariableAsyncBase.cs
2025-07-29 13:45:50 +03:00

185 lines
6.5 KiB
C#

using System;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.Jobs;
using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.State;
using UnityEngine.XR.Interaction.Toolkit.Utilities.Collections;
namespace UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables
{
/// <summary>
/// Async implementation of base TweenableVariable.
/// Uses affordance system jobs to asynchronously tween towards a target value.
/// </summary>
/// <typeparam name="T">BindableVariable type.</typeparam>
/// <seealso cref="TweenableVariableSynchronousBase{T}"/>
/// <remarks>
/// While the destructor of this class should be able to automatically dispose of allocated resources, it is best practice
/// to manually call dispose when instances are no longer needed.
/// </remarks>
[Obsolete("The Affordance System namespace and all associated classes have been deprecated. The existing affordance system will be moved, replaced and updated with a new interaction feedback system in a future version of XRI.")]
public abstract class TweenableVariableAsyncBase<T> : TweenableVariableBase<T>, IDisposable where T : struct, IEquatable<T>
{
/// <summary>
/// The internal tweenable variable value. When setting the value, subscribers may be notified.
/// If any async jobs are pending, they will be completed and the state will sync up.
/// The subscribers will not be notified if this variable is initialized, is configured to check for equality,
/// and the new value is equivalent.
/// </summary>
public new T Value
{
get => base.Value;
set
{
if (m_HasJobPending && m_OutputInitialized)
{
// Force complete any lingering jobs
CompleteJob();
// Sync up the current state of the output store
m_JobOutputStore[0] = value;
}
base.Value = value;
}
}
bool m_OutputInitialized;
NativeArray<T> m_JobOutputStore;
bool m_CurveDirty = true;
NativeCurve m_NativeCurve;
bool m_HasJobPending;
JobHandle m_LastJobHandle;
/// <summary>
/// Free up allocated memory.
/// </summary>
public void Dispose()
{
if (m_OutputInitialized)
{
UpdateStateFromCompletedJob();
m_JobOutputStore.Dispose();
m_OutputInitialized = false;
}
if (m_NativeCurve.isCreated)
{
m_NativeCurve.Dispose();
m_CurveDirty = true;
}
}
/// <summary>
/// Get the Burst friendly representation of the animation curve reference.
/// </summary>
/// <returns>Returns the Burst friendly representation of the animation curve reference.</returns>
NativeCurve GetNativeCurve()
{
RefreshCurve();
return m_NativeCurve;
}
/// <summary>
/// Refresh internal data to ensure it is ready to package for a job.
/// </summary>
void RefreshCurve()
{
if (m_CurveDirty || !m_NativeCurve.isCreated)
{
m_NativeCurve.Update(animationCurve, 1024);
m_CurveDirty = false;
}
}
/// <inheritdoc />
protected override void PreprocessTween()
{
base.PreprocessTween();
// We have to update the state from the previous job before getting the new value from which to start the next tween.
UpdateStateFromCompletedJob();
}
/// <inheritdoc />
protected override void ExecuteTween(T startValue, T targetValue, float tweenAmount, bool useCurve = false)
{
if (tweenAmount > k_NearlyOne)
{
Value = targetValue;
return;
}
// If using curve, we want to use it to compute an adjusted state transition target.
// This is necessary to play animations using the curve.
var originValue = useCurve ? startValue : targetValue;
var tweenInterpolationAmount = useCurve ? 1f : tweenAmount;
var stateTransitionIncrement = useCurve ? (byte)math.ceil(tweenAmount * AffordanceStateData.totalStateTransitionIncrements) : AffordanceStateData.totalStateTransitionIncrements;
var jobData = new TweenJobData<T>
{
initialValue = initialValue,
stateOriginValue = originValue,
stateTargetValue = targetValue,
stateTransitionIncrement = stateTransitionIncrement,
nativeCurve = GetNativeCurve(),
tweenStartValue = startValue,
tweenAmount = tweenInterpolationAmount,
outputData = GetJobOutputStore(),
};
m_LastJobHandle = ScheduleTweenJob(ref jobData);
m_HasJobPending = true;
}
void UpdateStateFromCompletedJob()
{
if (!CompleteJob())
return;
Value = GetJobOutputStore()[0];
}
/// <summary>
/// Generate the tween job from the given job data and schedule the job for execution on a worker thread.
/// </summary>
/// <param name="jobData">Typed job data used in tween job.</param>
/// <returns>Returns the handle identifying the scheduled job.</returns>
protected abstract JobHandle ScheduleTweenJob(ref TweenJobData<T> jobData);
NativeArray<T> GetJobOutputStore()
{
if (!m_OutputInitialized)
{
m_JobOutputStore = new NativeArray<T>(1, Allocator.Persistent);
m_OutputInitialized = true;
// Register to auto dispose.
DisposableManagerSingleton.RegisterDisposable(this);
}
return m_JobOutputStore;
}
/// <inheritdoc />
protected override void OnAnimationCurveChanged(AnimationCurve value)
{
base.OnAnimationCurveChanged(value);
m_CurveDirty = true;
}
bool CompleteJob()
{
if (!m_OutputInitialized || !m_HasJobPending)
return false;
m_LastJobHandle.Complete();
m_LastJobHandle = default;
m_HasJobPending = false;
return true;
}
}
}