using System; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; namespace LitMotion { internal interface IUpdateRunner { IMotionStorage Storage { get; } public void Update(double time, double unscaledTime, double realtime); public void Reset(); } internal sealed class UpdateRunner : IUpdateRunner where TValue : unmanaged where TOptions : unmanaged, IMotionOptions where TAdapter : unmanaged, IMotionAdapter { public UpdateRunner(MotionStorage storage, double time, double unscaledTime, double realtime) { this.storage = storage; prevTime = time; prevUnscaledTime = unscaledTime; prevRealtime = realtime; } readonly MotionStorage storage; double prevTime; double prevUnscaledTime; double prevRealtime; public MotionStorage Storage => storage; IMotionStorage IUpdateRunner.Storage => storage; public unsafe void Update(double time, double unscaledTime, double realtime) { var count = storage.Count; using var output = new NativeArray(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); using var completedIndexList = new NativeList(count, Allocator.TempJob); var deltaTime = time - prevTime; var unscaledDeltaTime = unscaledTime - prevUnscaledTime; var realDeltaTime = realtime - prevRealtime; prevTime = time; prevUnscaledTime = unscaledTime; prevRealtime = realtime; fixed (MotionData* dataPtr = storage.GetDataSpan()) { // update data var job = new MotionUpdateJob() { DataPtr = dataPtr, DeltaTime = deltaTime, UnscaledDeltaTime = unscaledDeltaTime, RealDeltaTime = realDeltaTime, Output = output, CompletedIndexList = completedIndexList.AsParallelWriter() }; job.Schedule(count, 16).Complete(); // invoke delegates var managedDataSpan = storage.GetManagedDataSpan(); var outputPtr = (TValue*)output.GetUnsafePtr(); for (int i = 0; i < managedDataSpan.Length; i++) { var currentDataPtr = dataPtr + i; ref var state = ref currentDataPtr->Core.State; if (state.IsInSequence) continue; var status = state.Status; ref var managedData = ref managedDataSpan[i]; bool isPlaying = status == MotionStatus.Playing; bool isCompleted = status == MotionStatus.Completed; bool isDelayed = status == MotionStatus.Delayed; bool canUpdate = isPlaying || isCompleted || (isDelayed && !managedData.SkipValuesDuringDelay); bool loopComplete = (isPlaying || isCompleted || isDelayed) && state.WasLoopCompleted; if (canUpdate) { try { managedData.UpdateUnsafe(outputPtr[i]); } catch (Exception ex) { MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex); if (managedData.CancelOnError) { state.Status = MotionStatus.Canceled; managedData.OnCancelAction?.Invoke(); } } } if (loopComplete) managedData.InvokeOnLoopComplete(state.CompletedLoops); if (isCompleted && state.WasStatusChanged) managedData.InvokeOnComplete(); } } storage.RemoveAll(completedIndexList); } public void Reset() { prevTime = 0; prevUnscaledTime = 0; prevRealtime = 0; storage.Reset(); } } }