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
{
///
/// Async implementation of base TweenableVariable.
/// Uses affordance system jobs to asynchronously tween towards a target value.
///
/// BindableVariable type.
///
///
/// 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.
///
[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 : TweenableVariableBase, IDisposable where T : struct, IEquatable
{
///
/// 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.
///
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 m_JobOutputStore;
bool m_CurveDirty = true;
NativeCurve m_NativeCurve;
bool m_HasJobPending;
JobHandle m_LastJobHandle;
///
/// Free up allocated memory.
///
public void Dispose()
{
if (m_OutputInitialized)
{
UpdateStateFromCompletedJob();
m_JobOutputStore.Dispose();
m_OutputInitialized = false;
}
if (m_NativeCurve.isCreated)
{
m_NativeCurve.Dispose();
m_CurveDirty = true;
}
}
///
/// Get the Burst friendly representation of the animation curve reference.
///
/// Returns the Burst friendly representation of the animation curve reference.
NativeCurve GetNativeCurve()
{
RefreshCurve();
return m_NativeCurve;
}
///
/// Refresh internal data to ensure it is ready to package for a job.
///
void RefreshCurve()
{
if (m_CurveDirty || !m_NativeCurve.isCreated)
{
m_NativeCurve.Update(animationCurve, 1024);
m_CurveDirty = false;
}
}
///
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();
}
///
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
{
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];
}
///
/// Generate the tween job from the given job data and schedule the job for execution on a worker thread.
///
/// Typed job data used in tween job.
/// Returns the handle identifying the scheduled job.
protected abstract JobHandle ScheduleTweenJob(ref TweenJobData jobData);
NativeArray GetJobOutputStore()
{
if (!m_OutputInitialized)
{
m_JobOutputStore = new NativeArray(1, Allocator.Persistent);
m_OutputInitialized = true;
// Register to auto dispose.
DisposableManagerSingleton.RegisterDisposable(this);
}
return m_JobOutputStore;
}
///
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;
}
}
}