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; } } }