using Unity.Burst;
using Unity.Burst.CompilerServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
namespace LitMotion
{
///
/// A job that updates the status of the motion data and outputs the current value.
///
/// The type of value to animate
/// The type of special parameters given to the motion data
/// The type of adapter that support value animation
[BurstCompile]
public unsafe struct MotionUpdateJob : IJobParallelFor
where TValue : unmanaged
where TOptions : unmanaged, IMotionOptions
where TAdapter : unmanaged, IMotionAdapter
{
[NativeDisableUnsafePtrRestriction] public MotionData* DataPtr;
[ReadOnly] public double DeltaTime;
[ReadOnly] public double UnscaledDeltaTime;
[ReadOnly] public double RealDeltaTime;
[WriteOnly] public NativeList.ParallelWriter CompletedIndexList;
[WriteOnly] public NativeArray Output;
public void Execute([AssumeRange(0, int.MaxValue)] int index)
{
var ptr = DataPtr + index;
var corePtr = (MotionDataCore*)ptr;
if (Hint.Likely(corePtr->Status is MotionStatus.Scheduled or MotionStatus.Delayed or MotionStatus.Playing))
{
var deltaTime = corePtr->TimeKind switch
{
MotionTimeKind.Time => DeltaTime,
MotionTimeKind.UnscaledTime => UnscaledDeltaTime,
MotionTimeKind.Realtime => RealDeltaTime,
_ => default
};
corePtr->Time = math.max(corePtr->Time + deltaTime * corePtr->PlaybackSpeed, 0.0);
var motionTime = corePtr->Time;
double t;
bool isCompleted;
bool isDelayed;
int completedLoops;
int clampedCompletedLoops;
if (Hint.Unlikely(corePtr->Duration <= 0f))
{
if (corePtr->DelayType == DelayType.FirstLoop || corePtr->Delay == 0f)
{
var time = motionTime - corePtr->Delay;
isCompleted = corePtr->Loops >= 0 && time > 0f;
if (isCompleted)
{
t = 1f;
completedLoops = corePtr->Loops;
}
else
{
t = 0f;
completedLoops = time < 0f ? -1 : 0;
}
clampedCompletedLoops = corePtr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, corePtr->Loops);
isDelayed = time < 0;
}
else
{
completedLoops = (int)math.floor(motionTime / corePtr->Delay);
clampedCompletedLoops = corePtr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, corePtr->Loops);
isCompleted = corePtr->Loops >= 0 && clampedCompletedLoops > corePtr->Loops - 1;
isDelayed = !isCompleted;
t = isCompleted ? 1f : 0f;
}
}
else
{
if (corePtr->DelayType == DelayType.FirstLoop)
{
var time = motionTime - corePtr->Delay;
completedLoops = (int)math.floor(time / corePtr->Duration);
clampedCompletedLoops = corePtr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, corePtr->Loops);
isCompleted = corePtr->Loops >= 0 && clampedCompletedLoops > corePtr->Loops - 1;
isDelayed = time < 0f;
if (isCompleted)
{
t = 1f;
}
else
{
var currentLoopTime = time - corePtr->Duration * clampedCompletedLoops;
t = math.clamp(currentLoopTime / corePtr->Duration, 0f, 1f);
}
}
else
{
var currentLoopTime = math.fmod(motionTime, corePtr->Duration + corePtr->Delay) - corePtr->Delay;
completedLoops = (int)math.floor(motionTime / (corePtr->Duration + corePtr->Delay));
clampedCompletedLoops = corePtr->Loops < 0 ? math.max(0, completedLoops) : math.clamp(completedLoops, 0, corePtr->Loops);
isCompleted = corePtr->Loops >= 0 && clampedCompletedLoops > corePtr->Loops - 1;
isDelayed = currentLoopTime < 0;
if (isCompleted)
{
t = 1f;
}
else
{
t = math.clamp(currentLoopTime / corePtr->Duration, 0f, 1f);
}
}
}
float progress;
switch (corePtr->LoopType)
{
default:
case LoopType.Restart:
progress = GetEasedValue(corePtr, (float)t);
break;
case LoopType.Yoyo:
progress = GetEasedValue(corePtr, (float)t);
if ((clampedCompletedLoops + (int)t) % 2 == 1) progress = 1f - progress;
break;
case LoopType.Incremental:
progress = GetEasedValue(corePtr, 1f) * clampedCompletedLoops + GetEasedValue(corePtr, (float)math.fmod(t, 1f));
break;
}
var totalDuration = corePtr->DelayType == DelayType.FirstLoop
? corePtr->Delay + corePtr->Duration * corePtr->Loops
: (corePtr->Delay + corePtr->Duration) * corePtr->Loops;
if (corePtr->Loops > 0 && motionTime >= totalDuration)
{
corePtr->Status = MotionStatus.Completed;
}
else if (isDelayed)
{
corePtr->Status = MotionStatus.Delayed;
}
else
{
corePtr->Status = MotionStatus.Playing;
}
var context = new MotionEvaluationContext()
{
Progress = progress
};
Output[index] = default(TAdapter).Evaluate(ref ptr->StartValue, ref ptr->EndValue, ref ptr->Options, context);
}
else if (corePtr->Status is MotionStatus.Completed or MotionStatus.Canceled)
{
CompletedIndexList.AddNoResize(index);
corePtr->Status = MotionStatus.Disposed;
}
}
static float GetEasedValue(MotionDataCore* data, float value)
{
return data->Ease switch
{
Ease.CustomAnimationCurve => data->AnimationCurve.Evaluate(value),
_ => EaseUtility.Evaluate(value, data->Ease)
};
}
}
}