This repository has been archived on 2025-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs
Unity Technologies 0f7a30d285 com.unity.netcode.gameobjects@1.0.0-pre.10
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.10] - 2022-06-21

### Added

- Added a new `OnTransportFailure` callback to `NetworkManager`. This callback is invoked when the manager's `NetworkTransport` encounters an unrecoverable error. Transport failures also cause the `NetworkManager` to shut down. Currently, this is only used by `UnityTransport` to signal a timeout of its connection to the Unity Relay servers. (#1994)
- Added `NetworkEvent.TransportFailure`, which can be used by implementations of `NetworkTransport` to signal to `NetworkManager` that an unrecoverable error was encountered. (#1994)
- Added test to ensure a warning occurs when nesting NetworkObjects in a NetworkPrefab (#1969)
- Added `NetworkManager.RemoveNetworkPrefab(...)` to remove a prefab from the prefabs list (#1950)

### Changed

- Updated `UnityTransport` dependency on `com.unity.transport` to 1.1.0. (#2025)
- (API Breaking) `ConnectionApprovalCallback` is no longer an `event` and will not allow more than 1 handler registered at a time. Also, `ConnectionApprovalCallback` is now a `Func<>` taking `ConnectionApprovalRequest` in and returning `ConnectionApprovalResponse` back out (#1972)

### Removed

### Fixed
- Fixed issue where dynamically spawned `NetworkObject`s could throw an exception if the scene of origin handle was zero (0) and the `NetworkObject` was already spawned. (#2017)
- Fixed issue where `NetworkObject.Observers` was not being cleared when despawned. (#2009)
- Fixed `NetworkAnimator` could not run in the server authoritative mode. (#2003)
- Fixed issue where late joining clients would get a soft synchronization error if any in-scene placed NetworkObjects were parented under another `NetworkObject`. (#1985)
- Fixed issue where `NetworkBehaviourReference` would throw a type cast exception if using `NetworkBehaviourReference.TryGet` and the component type was not found. (#1984)
- Fixed `NetworkSceneManager` was not sending scene event notifications for the currently active scene and any additively loaded scenes when loading a new scene in `LoadSceneMode.Single` mode. (#1975)
- Fixed issue where one or more clients disconnecting during a scene event would cause `LoadEventCompleted` or `UnloadEventCompleted` to wait until the `NetworkConfig.LoadSceneTimeOut` period before being triggered. (#1973)
- Fixed issues when multiple `ConnectionApprovalCallback`s were registered (#1972)
- Fixed a regression in serialization support: `FixedString`, `Vector2Int`, and `Vector3Int` types can now be used in NetworkVariables and RPCs again without requiring a `ForceNetworkSerializeByMemcpy<>` wrapper. (#1961)
- Fixed generic types that inherit from NetworkBehaviour causing crashes at compile time. (#1976)
- Fixed endless dialog boxes when adding a `NetworkBehaviour` to a `NetworkManager` or vice-versa. (#1947)
- Fixed `NetworkAnimator` issue where it was only synchronizing parameters if the layer or state changed or was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where when it did detect a parameter had changed it would send all parameters as opposed to only the parameters that changed. (#1946)
- Fixed `NetworkAnimator` issue where it was not always disposing the `NativeArray` that is allocated when spawned. (#1946)
- Fixed `NetworkAnimator` issue where it was not taking the animation speed or state speed multiplier into consideration. (#1946)
- Fixed `NetworkAnimator` issue where it was not properly synchronizing late joining clients if they joined while `Animator` was transitioning between states. (#1946)
- Fixed `NetworkAnimator` issue where the server was not relaying changes to non-owner clients when a client was the owner. (#1946)
- Fixed issue where the `PacketLoss` metric for tools would return the packet loss over a connection lifetime instead of a single frame. (#2004)
2022-06-21 00:00:00 +00:00

295 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// Solves for incoming values that are jittered
/// Partially solves for message loss. Unclamped lerping helps hide this, but not completely
/// </summary>
public abstract class BufferedLinearInterpolator<T> where T : struct
{
internal float MaxInterpolationBound = 3.0f;
private struct BufferedItem
{
public T Item;
public double TimeSent;
public BufferedItem(T item, double timeSent)
{
Item = item;
TimeSent = timeSent;
}
}
/// <summary>
/// Theres two factors affecting interpolation: buffering (set in NetworkManagers NetworkTimeSystem) and interpolation time, which is the amount of time itll take to reach the target. This is to affect the second one.
/// </summary>
public float MaximumInterpolationTime = 0.1f;
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
private T m_InterpStartValue;
private T m_CurrentInterpValue;
private T m_InterpEndValue;
private double m_EndTimeConsumed;
private double m_StartTimeConsumed;
private readonly List<BufferedItem> m_Buffer = new List<BufferedItem>(k_BufferCountLimit);
// Buffer consumption scenarios
// Perfect case consumption
// | 1 | 2 | 3 |
// | 2 | 3 | 4 | consume 1
// | 3 | 4 | 5 | consume 2
// | 4 | 5 | 6 | consume 3
// | 5 | 6 | 7 | consume 4
// jittered case
// | 1 | 2 | 3 |
// | 2 | 3 | | consume 1
// | 3 | | | consume 2
// | 4 | 5 | 6 | consume 3
// | 5 | 6 | 7 | consume 4
// bursted case (assuming max count is 5)
// | 1 | 2 | 3 |
// | 2 | 3 | | consume 1
// | 3 | | | consume 2
// | | | | consume 3
// | | | |
// | 4 | 5 | 6 | 7 | 8 | --> consume all and teleport to last value <8> --> this is the nuclear option, ideally this example would consume 4 and 5
// instead of jumping to 8, but since in OnValueChange we don't yet have an updated server time (updated in pre-update) to know which value
// we should keep and which we should drop, we don't have enough information to do this. Another thing would be to not have the burst in the first place.
// Constant absolute value for max buffer count instead of dynamic time based value. This is in case we have very low tick rates, so
// that we don't have a very small buffer because of this.
private const int k_BufferCountLimit = 100;
private BufferedItem m_LastBufferedItemReceived;
private int m_NbItemsReceivedThisFrame;
private int m_LifetimeConsumedCount;
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
/// <summary>
/// Resets Interpolator to initial state
/// </summary>
public void Clear()
{
m_Buffer.Clear();
m_EndTimeConsumed = 0.0d;
m_StartTimeConsumed = 0.0d;
}
/// <summary>
/// Teleports current interpolation value to targetValue.
/// </summary>
public void ResetTo(T targetValue, double serverTime)
{
m_LifetimeConsumedCount = 1;
m_InterpStartValue = targetValue;
m_InterpEndValue = targetValue;
m_CurrentInterpValue = targetValue;
m_Buffer.Clear();
m_EndTimeConsumed = 0.0d;
m_StartTimeConsumed = 0.0d;
Update(0, serverTime, serverTime);
}
// todo if I have value 1, 2, 3 and I'm treating 1 to 3, I shouldn't interpolate between 1 and 3, I should interpolate from 1 to 2, then from 2 to 3 to get the best path
private void TryConsumeFromBuffer(double renderTime, double serverTime)
{
int consumedCount = 0;
// only consume if we're ready
// this operation was measured as one of our most expensive, and we should put some thought into this.
// NetworkTransform has (currently) 7 buffered linear interpolators (3 position, 3 scale, 1 rot), and
// each has its own independent buffer and 'm_endTimeConsume'. That means every frame I have to do 7x
// these checks vs. if we tracked these values in a unified way
if (renderTime >= m_EndTimeConsumed)
{
BufferedItem? itemToInterpolateTo = null;
// assumes we're using sequenced messages for netvar syncing
// buffer contains oldest values first, iterating from end to start to remove elements from list while iterating
// calling m_Buffer.Count shows up hot in the profiler.
for (int i = m_Buffer.Count - 1; i >= 0; i--) // todo stretch: consume ahead if we see we're missing values due to packet loss
{
var bufferedValue = m_Buffer[i];
// Consume when ready and interpolate to last value we can consume. This can consume multiple values from the buffer
if (bufferedValue.TimeSent <= serverTime)
{
if (!itemToInterpolateTo.HasValue || bufferedValue.TimeSent > itemToInterpolateTo.Value.TimeSent)
{
if (m_LifetimeConsumedCount == 0)
{
// if interpolator not initialized, teleport to first value when available
m_StartTimeConsumed = bufferedValue.TimeSent;
m_InterpStartValue = bufferedValue.Item;
}
else if (consumedCount == 0)
{
// Interpolating to new value, end becomes start. We then look in our buffer for a new end.
m_StartTimeConsumed = m_EndTimeConsumed;
m_InterpStartValue = m_InterpEndValue;
}
if (bufferedValue.TimeSent > m_EndTimeConsumed)
{
itemToInterpolateTo = bufferedValue;
m_EndTimeConsumed = bufferedValue.TimeSent;
m_InterpEndValue = bufferedValue.Item;
}
}
m_Buffer.RemoveAt(i);
consumedCount++;
m_LifetimeConsumedCount++;
}
}
}
}
/// <summary>
/// Convenience version of 'Update' mainly for testing
/// the reason we don't want to always call this version is so that on the calling side we can compute
/// the renderTime once for the many things being interpolated (and the many interpolators per object)
/// </summary>
/// <param name="deltaTime">time since call</param>
/// <param name="serverTime">current server time</param>
public T Update(float deltaTime, NetworkTime serverTime)
{
return Update(deltaTime, serverTime.TimeTicksAgo(1).Time, serverTime.Time);
}
/// <summary>
/// Call to update the state of the interpolators before reading out
/// </summary>
/// <param name="deltaTime">time since last call</param>
/// <param name="renderTime">our current time</param>
/// <param name="serverTime">current server time</param>
public T Update(float deltaTime, double renderTime, double serverTime)
{
TryConsumeFromBuffer(renderTime, serverTime);
if (InvalidState)
{
throw new InvalidOperationException("trying to update interpolator when no data has been added to it yet");
}
// Interpolation example to understand the math below
// 4 4.5 6 6.5
// | | | |
// A render B Server
if (m_LifetimeConsumedCount >= 1) // shouldn't interpolate between default values, let's wait to receive data first, should only interpolate between real measurements
{
float t = 1.0f;
double range = m_EndTimeConsumed - m_StartTimeConsumed;
if (range > k_SmallValue)
{
t = (float)((renderTime - m_StartTimeConsumed) / range);
if (t < 0.0f)
{
// There is no mechanism to guarantee renderTime to not be before m_StartTimeConsumed
// This clamps t to a minimum of 0 and fixes issues with longer frames and pauses
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogError($"renderTime was before m_StartTimeConsumed. This should never happen. {nameof(renderTime)} is {renderTime}, {nameof(m_StartTimeConsumed)} is {m_StartTimeConsumed}");
}
t = 0.0f;
}
if (t > MaxInterpolationBound) // max extrapolation
{
// TODO this causes issues with teleport, investigate
t = 1.0f;
}
}
var target = InterpolateUnclamped(m_InterpStartValue, m_InterpEndValue, t);
m_CurrentInterpValue = Interpolate(m_CurrentInterpValue, target, deltaTime / MaximumInterpolationTime); // second interpolate to smooth out extrapolation jumps
}
m_NbItemsReceivedThisFrame = 0;
return m_CurrentInterpValue;
}
/// <summary>
/// Add measurements to be used during interpolation. These will be buffered before being made available to be displayed as "latest value".
/// </summary>
public void AddMeasurement(T newMeasurement, double sentTime)
{
m_NbItemsReceivedThisFrame++;
// This situation can happen after a game is paused. When starting to receive again, the server will have sent a bunch of messages in the meantime
// instead of going through thousands of value updates just to get a big teleport, we're giving up on interpolation and teleporting to the latest value
if (m_NbItemsReceivedThisFrame > k_BufferCountLimit)
{
if (m_LastBufferedItemReceived.TimeSent < sentTime)
{
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
ResetTo(newMeasurement, sentTime);
// Next line keeps renderTime above m_StartTimeConsumed. Fixes pause/unpause issues
m_Buffer.Add(m_LastBufferedItemReceived);
}
return;
}
if (sentTime > m_EndTimeConsumed || m_LifetimeConsumedCount == 0) // treat only if value is newer than the one being interpolated to right now
{
m_LastBufferedItemReceived = new BufferedItem(newMeasurement, sentTime);
m_Buffer.Add(m_LastBufferedItemReceived);
}
}
/// <summary>
/// Gets latest value from the interpolator. This is updated every update as time goes by.
/// </summary>
public T GetInterpolatedValue()
{
return m_CurrentInterpValue;
}
/// <summary>
/// Method to override and adapted to the generic type. This assumes interpolation for that value will be clamped.
/// </summary>
protected abstract T Interpolate(T start, T end, float time);
/// <summary>
/// Method to override and adapted to the generic type. This assumes interpolation for that value will not be clamped.
/// </summary>
protected abstract T InterpolateUnclamped(T start, T end, float time);
}
public class BufferedLinearInterpolatorFloat : BufferedLinearInterpolator<float>
{
protected override float InterpolateUnclamped(float start, float end, float time)
{
return Mathf.LerpUnclamped(start, end, time);
}
protected override float Interpolate(float start, float end, float time)
{
return Mathf.Lerp(start, end, time);
}
}
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
{
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
{
return Quaternion.SlerpUnclamped(start, end, time);
}
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
{
return Quaternion.SlerpUnclamped(start, end, time);
}
}
}