Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Time property and Preserve() method to MotionHandle #146

Merged
merged 11 commits into from
Nov 23, 2024
5 changes: 5 additions & 0 deletions src/LitMotion/Assets/LitMotion/Runtime/Internal/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public static void PlaybackSpeedMustBeZeroOrGreater()
throw new ArgumentOutOfRangeException("Playback speed must be 0 or greater.");
}

public static void TimeMustBeZeroOrGreater()
{
throw new ArgumentOutOfRangeException("Time must be 0 or greater.");
}

public static void MotionNotExists()
{
throw new InvalidOperationException("Motion has been destroyed or no longer exists.");
Expand Down
9 changes: 5 additions & 4 deletions src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ namespace LitMotion
[StructLayout(LayoutKind.Sequential)]
public struct MotionDataCore
{
// state
public MotionStatus Status;

public double Time;
public float PlaybackSpeed;
public float Duration;
public bool IsPreserved;
public bool WasStatusChanged;

// parameters
public float Duration;
public Ease Ease;

#if LITMOTION_COLLECTIONS_2_0_OR_NEWER
public NativeAnimationCurve AnimationCurve;
#else
public UnsafeAnimationCurve AnimationCurve;
#endif

public MotionTimeKind TimeKind;
public float Delay;
public int Loops;
Expand Down
163 changes: 163 additions & 0 deletions src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using Unity.Burst;
using Unity.Burst.CompilerServices;
using Unity.Mathematics;

namespace LitMotion
{
[BurstCompile]
internal unsafe static class MotionHelper
{
[BurstCompile]
public static void SetTime<TValue, TOptions, TAdapter>(MotionData<TValue, TOptions>* ptr, double time, out TValue result)
where TValue : unmanaged
where TOptions : unmanaged, IMotionOptions
where TAdapter : unmanaged, IMotionAdapter<TValue, TOptions>
{
var corePtr = (MotionDataCore*)ptr;
var prevStatus = corePtr->Status;

// reset flag(s)
corePtr->WasStatusChanged = false;

corePtr->Time = time;
time = math.max(time, 0.0);

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 timeSinceStart = time - corePtr->Delay;
isCompleted = corePtr->Loops >= 0 && timeSinceStart > 0f;
if (isCompleted)
{
t = 1f;
completedLoops = corePtr->Loops;
}
else
{
t = 0f;
completedLoops = timeSinceStart < 0f ? -1 : 0;
}
clampedCompletedLoops = corePtr->Loops < 0
? math.max(0, completedLoops)
: math.clamp(completedLoops, 0, corePtr->Loops);
isDelayed = timeSinceStart < 0;
}
else
{
completedLoops = (int)math.floor(time / 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 timeSinceStart = time - corePtr->Delay;
completedLoops = (int)math.floor(timeSinceStart / 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 = timeSinceStart < 0f;

if (isCompleted)
{
t = 1f;
}
else
{
var currentLoopTime = timeSinceStart - corePtr->Duration * clampedCompletedLoops;
t = math.clamp(currentLoopTime / corePtr->Duration, 0f, 1f);
}
}
else
{
var currentLoopTime = math.fmod(time, corePtr->Duration + corePtr->Delay) - corePtr->Delay;
completedLoops = (int)math.floor(time / (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.Flip:
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;
case LoopType.Yoyo:
progress = (clampedCompletedLoops + (int)t) % 2 == 1
? GetEasedValue(corePtr, (float)(1f - t))
: GetEasedValue(corePtr, (float)t);
break;
}

var totalDuration = corePtr->DelayType == DelayType.FirstLoop
? corePtr->Delay + corePtr->Duration * corePtr->Loops
: (corePtr->Delay + corePtr->Duration) * corePtr->Loops;

if (isCompleted)
{
corePtr->Status = MotionStatus.Completed;
}
else if (isDelayed)
{
corePtr->Status = MotionStatus.Delayed;
}
else
{
corePtr->Status = MotionStatus.Playing;
}

corePtr->WasStatusChanged = prevStatus != corePtr->Status;

var context = new MotionEvaluationContext()
{
Progress = progress
};

result = default(TAdapter).Evaluate(ref ptr->StartValue, ref ptr->EndValue, ref ptr->Options, context);
}

static float GetEasedValue(MotionDataCore* data, float value)
{
return data->Ease switch
{
Ease.CustomAnimationCurve => data->AnimationCurve.Evaluate(value),
_ => EaseUtility.Evaluate(value, data->Ease)
};
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ public static bool IsActive(MotionHandle handle)
return list[handle.StorageId].IsActive(handle);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetTime(MotionHandle handle, double time)
{
CheckTypeId(handle);
list[handle.StorageId].SetTime(handle, time);
}

// For MotionTracker
public static (Type ValueType, Type OptionsType, Type AdapterType) GetMotionType(MotionHandle handle)
{
Expand Down
34 changes: 31 additions & 3 deletions src/LitMotion/Assets/LitMotion/Runtime/Internal/MotionStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal interface IMotionStorage
bool TryComplete(MotionHandle handle);
void Cancel(MotionHandle handle);
void Complete(MotionHandle handle);
void SetTime(MotionHandle handle, double time);
ref MotionDataCore GetDataRef(MotionHandle handle);
ref ManagedMotionData GetManagedDataRef(MotionHandle handle);
void Reset();
Expand Down Expand Up @@ -71,9 +72,10 @@ public unsafe MotionHandle Create(ref MotionBuilder<TValue, TOptions, TAdapter>
ref var dataRef = ref unmanagedDataArray[tail];
ref var managedDataRef = ref managedDataArray[tail];

dataRef.Core.Status = MotionStatus.Scheduled;
dataRef.Core.Time = 0;
dataRef.Core.PlaybackSpeed = 1f;
dataRef.Core.Status = MotionStatus.Scheduled;
dataRef.Core.IsPreserved = false;

dataRef.Core.Duration = buffer.Duration;
dataRef.Core.Delay = buffer.Delay;
Expand Down Expand Up @@ -308,8 +310,7 @@ int TryCompleteCore(MotionHandle handle)

ref var managedData = ref managedDataArray[denseIndex];

// To avoid duplication of Complete processing, it is treated as canceled internally.
unmanagedData.Core.Status = MotionStatus.Canceled;
unmanagedData.Core.Status = MotionStatus.Completed;

var endProgress = unmanagedData.Core.LoopType switch
{
Expand Down Expand Up @@ -353,6 +354,33 @@ int TryCompleteCore(MotionHandle handle)
return 0;
}

public unsafe void SetTime(MotionHandle handle, double time)
{
ref var slot = ref sparseSetCore.GetSlotRefUnchecked(handle.Index);

var denseIndex = slot.DenseIndex;
if (denseIndex < 0 || denseIndex >= tail) Error.MotionNotExists();

fixed (MotionData<TValue, TOptions>* ptr = unmanagedDataArray)
{
var dataPtr = ptr + denseIndex;

var version = slot.Version;
if (version <= 0 || version != handle.Version) Error.MotionNotExists();

MotionHelper.SetTime<TValue, TOptions, TAdapter>(dataPtr + denseIndex, time, out var result);

try
{
managedDataArray[denseIndex].UpdateUnsafe(result);
}
catch (Exception ex)
{
MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex);
}
}
}

public ref ManagedMotionData GetManagedDataRef(MotionHandle handle)
{
ref var slot = ref GetSlotWithVarify(handle);
Expand Down
22 changes: 13 additions & 9 deletions src/LitMotion/Assets/LitMotion/Runtime/Internal/UpdateRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public unsafe void Update(double time, double unscaledTime, double realtime)
var outputPtr = (TValue*)output.GetUnsafePtr();
for (int i = 0; i < managedDataSpan.Length; i++)
{
var status = (dataPtr + i)->Core.Status;
var currentDataPtr = dataPtr + i;
var status = currentDataPtr->Core.Status;
ref var managedData = ref managedDataSpan[i];
if (status == MotionStatus.Playing || (status == MotionStatus.Delayed && !managedData.SkipValuesDuringDelay))
{
Expand All @@ -75,7 +76,7 @@ public unsafe void Update(double time, double unscaledTime, double realtime)
MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex);
if (managedData.CancelOnError)
{
(dataPtr + i)->Core.Status = MotionStatus.Canceled;
currentDataPtr->Core.Status = MotionStatus.Canceled;
managedData.OnCancelAction?.Invoke();
}
}
Expand All @@ -91,19 +92,22 @@ public unsafe void Update(double time, double unscaledTime, double realtime)
MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex);
if (managedData.CancelOnError)
{
(dataPtr + i)->Core.Status = MotionStatus.Canceled;
currentDataPtr->Core.Status = MotionStatus.Canceled;
managedData.OnCancelAction?.Invoke();
continue;
}
}

try
if (currentDataPtr->Core.WasStatusChanged)
{
managedData.OnCompleteAction?.Invoke();
}
catch (Exception ex)
{
MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex);
try
{
managedData.OnCompleteAction?.Invoke();
}
catch (Exception ex)
{
MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex);
}
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/LitMotion/Assets/LitMotion/Runtime/MotionHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ public struct MotionHandle : IEquatable<MotionHandle>
/// </summary>
public int Version;

/// <summary>
/// Motion time
/// </summary>
public readonly double Time
{
get
{
return MotionManager.GetDataRef(this).Time;
}
set
{
if (value < 0f) Error.TimeMustBeZeroOrGreater();
MotionManager.SetTime(this, value);
}
}

/// <summary>
/// Motion playback speed.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public static bool IsActive(this MotionHandle handle)
return MotionManager.IsActive(handle);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Preserve(this MotionHandle handle)
{
MotionManager.GetDataRef(handle).IsPreserved = true;
}

/// <summary>
/// Complete motion.
/// </summary>
Expand Down
Loading