internal override void Update()

in MREUnityRuntimeLib/Animation/Animation.cs [120:253]


		internal override void Update(long serverTime)
		{
			if (Data == null)
			{
				// only way for Data to be unset is if it's unloaded
				if (DataSet)
				{
					manager.DeregisterAnimation(this);
				}
				return;
			}

			// normalize time to animation length based on wrap settings
			float currentTime;
			if (Weight > 0 && Speed != 0)
			{
				// normal operation
				currentTime = (serverTime - BasisTime) * Speed / 1000;
				LastWeight = Weight;
				StopUpdating = false;
			}
			else if (!StopUpdating)
			{
				// supposed to stop, but run one last update
				currentTime = Time;
				StopUpdating = true;
			}
			else
			{
				// don't update
				return;
			}

			currentTime = ApplyWrapMode(currentTime);

			// process tracks
			for (var ti = 0; ti < Data.Tracks.Length; ti++)
			{
				Track track = Data.Tracks[ti];
				bool usesPrevFrameValue = false, usesNextFrameValue = false;

				(Keyframe prevFrame, Keyframe nextFrame) = GetActiveKeyframes(ti, currentTime);

				// either no keyframes, or time out of range
				if (prevFrame == null)
				{
					continue;
				}

				float linearT = (currentTime - prevFrame.Time) / (nextFrame.Time - prevFrame.Time);

				// get realtime value for trailing frame
				JToken prevFrameValue = prevFrame.Value;
				if (prevFrame.ValuePath != null)
				{
					if (GetPatchAtPath(prevFrame.ValuePath, out IPatchable patch))
					{
						prevFrameValue = TokenPool.Lease(TargetPath.TypeOfPath[prevFrame.ValuePath.Path]);
						if (patch.ReadFromPath(prevFrame.ValuePath, ref prevFrameValue, 0))
						{
							usesPrevFrameValue = true;
						}
						else
						{
							TokenPool.Return(prevFrameValue);
							continue;
						}
					}
					else continue;
				}

				// get realtime value for leading frame (same as above)
				JToken nextFrameValue = nextFrame.Value;
				if (nextFrame.ValuePath != null)
				{
					if (GetPatchAtPath(nextFrame.ValuePath, out IPatchable patch))
					{
						nextFrameValue = TokenPool.Lease(TargetPath.TypeOfPath[nextFrame.ValuePath.Path]);
						if (patch.ReadFromPath(nextFrame.ValuePath, ref nextFrameValue, 0))
						{
							usesNextFrameValue = true;
						}
						else
						{
							TokenPool.Return(nextFrameValue);
							continue;
						}
					}
					else continue;
				}

				// compute new value for targeted field
				JToken outputToken = TokenPool.Lease(prevFrameValue);
				Interpolations.Interpolate(prevFrameValue, nextFrameValue, linearT, ref outputToken, nextFrame.Bezier ?? track.Bezier ?? LinearEasing);
				if (usesPrevFrameValue)
				{
					TokenPool.Return(prevFrameValue);
				}
				if (usesNextFrameValue)
				{
					TokenPool.Return(nextFrameValue);
				}

				// mix computed value with the result of any other anims targeting the same property
				AnimationManager.AnimBlend blendData = manager.AnimBlends.GetOrCreate(
					ResolvedTargetPaths[ti],
					() => new AnimationManager.AnimBlend(ResolvedTargetPaths[ti]));

				blendData.FinalUpdate = blendData.FinalUpdate || StopUpdating;
				if (blendData.TotalWeight == 0)
				{
					blendData.TotalWeight = LastWeight;
					if (blendData.CurrentValue == null)
					{
						blendData.CurrentValue = outputToken.DeepClone();
					}
					else
					{
						JToken temp = blendData.CurrentValue;
						blendData.CurrentValue = outputToken;
						TokenPool.Return(temp);
					}
				}
				else
				{
					blendData.TotalWeight += LastWeight;
					JToken temp = TokenPool.Lease(outputToken);
					Interpolations.Interpolate(outputToken, blendData.CurrentValue, LastWeight / blendData.TotalWeight, ref temp, LinearEasing);

					TokenPool.Return(blendData.CurrentValue);
					blendData.CurrentValue = temp;
				}
			}
		}