上传YomovSDK

This commit is contained in:
Sora丶kong
2026-03-03 03:15:46 +08:00
parent 9096da7e6c
commit eb97f31065
6477 changed files with 1932208 additions and 3 deletions

View File

@@ -0,0 +1,887 @@
//using GameKit.Utilities;
//using FishNet.Transporting;
//using FishNet.Utility.Extension;
//using System.Runtime.CompilerServices;
//using UnityEngine;
//using FishNet.Managing.Timing;
//using System.Collections.Generic;
//using FishNet.Managing.Scened;
//namespace FishNet.Object.Prediction
//{
// internal class AdaptiveInterpolationSmoother
// {
// #if PREDICTION_V2
// #region Types.
// /// <summary>
// /// Data on a goal to move towards.
// /// </summary>
// private class GoalData : IResettable
// {
// /// <summary>
// /// True if this GoalData is valid.
// /// </summary>
// public bool IsValid;
// /// <summary>
// /// Tick of the data this GoalData is for.
// /// </summary>
// public uint DataTick;
// /// <summary>
// /// Data on how fast to move to transform values.
// /// </summary>
// public RateData MoveRates = new RateData();
// /// <summary>
// /// Transform values to move towards.
// /// </summary>
// public TransformPropertiesCls TransformProperties = new TransformPropertiesCls();
// public GoalData() { }
// public void InitializeState() { }
// public void ResetState()
// {
// DataTick = 0;
// TransformProperties.ResetState();
// MoveRates.ResetState();
// IsValid = false;
// }
// /// <summary>
// /// Updates values using a GoalData.
// /// </summary>
// public void Update(GoalData gd)
// {
// DataTick = gd.DataTick;
// MoveRates.Update(gd.MoveRates);
// TransformProperties.Update(gd.TransformProperties);
// IsValid = true;
// }
// public void Update(uint dataTick, RateData rd, TransformPropertiesCls tp)
// {
// DataTick = dataTick;
// MoveRates = rd;
// TransformProperties = tp;
// IsValid = true;
// }
// }
// /// <summary>
// /// How fast to move to values.
// /// </summary>
// private class RateData : IResettable
// {
// /// <summary>
// /// Rate for position after smart calculations.
// /// </summary>
// public float Position;
// /// <summary>
// /// Rate for rotation after smart calculations.
// /// </summary>
// public float Rotation;
// /// <summary>
// /// Number of ticks the rates are calculated for.
// /// If TickSpan is 2 then the rates are calculated under the assumption the transform changed over 2 ticks.
// /// </summary>
// public uint TickSpan;
// /// <summary>
// /// Time remaining until transform is expected to reach it's goal.
// /// </summary>
// internal float TimeRemaining;
// public RateData() { }
// public void InitializeState() { }
// /// <summary>
// /// Resets values for re-use.
// /// </summary>
// public void ResetState()
// {
// Position = 0f;
// Rotation = 0f;
// TickSpan = 0;
// TimeRemaining = 0f;
// }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public void Update(RateData rd)
// {
// Update(rd.Position, rd.Rotation, rd.TickSpan, rd.TimeRemaining);
// }
// /// <summary>
// /// Updates rates.
// /// </summary>
// public void Update(float position, float rotation, uint tickSpan, float timeRemaining)
// {
// Position = position;
// Rotation = rotation;
// TickSpan = tickSpan;
// TimeRemaining = timeRemaining;
// }
// }
// #endregion
// #region Private.
// /// <summary>
// /// Offsets of the graphical object when this was initialized.
// /// </summary>
// private TransformProperties _graphicalInitializedValues;
// /// <summary>
// /// Offsets of the graphical object during PreTick. This could also be the offset PreReplay.
// /// </summary>
// private TransformProperties _graphicalPretickValues;
// /// <summary>
// /// Offsets of the root transform before simulating. This could be PreTick, or PreReplay.
// /// </summary>
// private TransformProperties _rootPreSimulateValues;
// /// <summary>
// /// SmoothingData to use.
// /// </summary>
// private AdaptiveInterpolationSmoothingData _smoothingData;
// /// <summary>
// /// Current interpolation value. This changes based on ping and settings.
// /// </summary>
// private long _currentInterpolation = 2;
// /// <summary>
// /// Target interpolation when collision is exited. This changes based on ping and settings.
// /// </summary>
// private uint _targetInterpolation;
// /// <summary>
// /// Target interpolation when collision is entered. This changes based on ping and settings.
// /// </summary>
// private uint _targetCollisionInterpolation;
// /// <summary>
// /// Current GoalData being used.
// /// </summary>
// private GoalData _currentGoalData = new GoalData();
// /// <summary>
// /// GoalDatas to move towards.
// /// </summary>
// //private RingBuffer<GoalData> _goalDatas = new RingBuffer<GoalData>();
// private List<GoalData> _goalDatas = new List<GoalData>();
// /// <summary>
// /// Last ping value when it was checked.
// /// </summary>
// private long _lastPing = long.MinValue;
// /// <summary>
// /// Cached NetworkObject reference in SmoothingData for performance.
// /// </summary>
// private NetworkObject _networkObject;
// #endregion
// #region Const.
// /// <summary>
// /// Multiplier to apply to movement speed when buffer is over interpolation.
// /// </summary>
// private const float OVERFLOW_MULTIPLIER = 0.1f;
// /// <summary>
// /// Multiplier to apply to movement speed when buffer is under interpolation.
// /// </summary>
// private const float UNDERFLOW_MULTIPLIER = 0.02f;
// #endregion
// public AdaptiveInterpolationSmoother()
// {
// /* Initialize for up to 50
// * goal datas. Anything beyond that
// * is unreasonable. */
// //_goalDatas.Initialize(50);
// }
// /// <summary>
// /// Initializes this for use.
// /// </summary>
// internal void Initialize(AdaptiveInterpolationSmoothingData data)
// {
// _smoothingData = data;
// _networkObject = data.NetworkObject;
// SetGraphicalObject(data.GraphicalObject);
// }
// /// <summary>
// /// <summary>
// /// Called every frame.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public void Update()
// {
// if (CanSmooth())
// MoveToTarget();
// }
// private string GetGoalDataTicks()
// {
// string result = string.Empty;
// foreach (var item in _goalDatas)
// result += item.DataTick + ", ";
// return result;
// }
// private string _preTickGoalDatas = string.Empty;
// /// <summary>
// /// Called when the TimeManager invokes OnPreTick.
// /// </summary>
// public void OnPreTick()
// {
// _preTickGoalDatas = GetGoalDataTicks();
// if (CanSmooth())
// {
// UpdatePingInterpolation();
// _graphicalPretickValues.Update(_smoothingData.GraphicalObject);
// //Update the last post simulate data.
// UpdateRootPreSimulateOffsets();
// //UpdateRootPreSimulateOffsets(_networkObject.ReplicateTick.Value(_networkObject.TimeManager) - 1);
// }
// }
// /// <summary>
// /// Called when the TimeManager invokes OnPostTick.
// /// </summary>
// public void OnPostTick()
// {
// if (CanSmooth())
// {
// //Move towards target interpolation.
// UpdateCurrentInterpolation();
// //Reset graphics to start graphicals transforms properties.
// _smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPretickValues.Position, _graphicalPretickValues.Rotation);
// //Create a goal data for new transform position.
// uint tick = _networkObject.LastUnorderedReplicateTick;
// //Debug.Log($"GoalDatas count {_goalDatas.Count}. Tick {tick}. LocalTick {_networkObject.TimeManager.LocalTick}");
// CreatePostSimulateGoalData(tick, true);
// }
// }
// /// <summary>
// /// Called before a reconcile runs a replay.
// /// </summary>
// public void OnPreReplicateReplay(uint clientTick, uint serverTick)
// {
// //Update the last post simulate data.
// if (CanSmooth())
// {
// UpdateRootPreSimulateOffsets();
// }
// }
// /// <summary>
// /// Called after a reconcile runs a replay.
// /// </summary>
// public void OnPostReplicateReplay(uint clientTick, uint serverTick)
// {
// if (CanSmooth())
// {
// /* Create new goal data from the replay.
// * This must be done every replay. If a desync
// * did occur then the goaldatas would be different
// * from what they were previously. */
// uint tick = _networkObject.LastUnorderedReplicateTick;
// CreatePostSimulateGoalData(tick, false);
// }
// }
// /// <summary>
// /// Updates rootPostSimulateOffsets value with root transform's current values.
// /// </summary>
// private void UpdateRootPreSimulateOffsets()
// {
// Transform t = _networkObject.transform;
// _rootPreSimulateValues.Update(t.position, t.rotation);
// }
// /// <summary>
// /// Moves current interpolation to target interpolation.
// /// </summary>
// private void UpdateCurrentInterpolation()
// {
// AdaptiveInterpolationSmoothingData data = _smoothingData;
// bool colliding = _networkObject.CollidingWithLocalClient();
// if (colliding)
// _currentInterpolation -= data.InterpolationDecreaseStep;
// else
// _currentInterpolation += data.InterpolationIncreaseStep;
// _currentInterpolation = (long)Mathf.Clamp(_currentInterpolation, _targetCollisionInterpolation, _targetInterpolation);
// }
// /// <summary>
// /// Updates interpolation values based on ping.
// /// </summary>
// private void UpdatePingInterpolation()
// {
// /* Only update if ping has changed considerably.
// * This will prevent random lag spikes from throwing
// * off the interpolation. */
// long ping = _networkObject.TimeManager.RoundTripTime;
// ulong difference = (ulong)Mathf.Abs(ping - _lastPing);
// _lastPing = ping;
// //Allow update if ping jump is large enough.
// if (difference > 25)
// SetTargetSmoothing(ping, false);
// }
// /// <summary>
// /// Sets target smoothing values.
// /// </summary>
// /// <param name="setImmediately">True to set current values to targets immediately.</param>
// private void SetTargetSmoothing(long ping, bool setImmediately)
// {
// AdaptiveInterpolationSmoothingData data = _smoothingData;
// TimeManager tm = _networkObject.TimeManager;
// double interpolationTime = (ping / 1000d) * data.InterpolationPercent;
// _targetInterpolation = tm.TimeToTicks(interpolationTime, TickRounding.RoundUp);
// double collisionInterpolationTime = (ping / 1000d) * data.CollisionInterpolationPercent;
// _targetCollisionInterpolation = tm.TimeToTicks(collisionInterpolationTime, TickRounding.RoundUp);
// //If to apply values to targets immediately.
// if (setImmediately)
// _currentInterpolation = (_networkObject.CollidingWithLocalClient()) ? _targetCollisionInterpolation : _targetInterpolation;
// }
// /// <summary>
// /// Sets GraphicalObject.
// /// </summary>
// /// <param name="value"></param>
// public void SetGraphicalObject(Transform value)
// {
// _smoothingData.GraphicalObject = value;
// _graphicalInitializedValues = _networkObject.transform.GetTransformOffsets(_smoothingData.GraphicalObject);
// }
// /// <summary>
// /// Returns if the graphics can be smoothed.
// /// </summary>
// /// <returns></returns>
// private bool CanSmooth()
// {
// if (_networkObject.IsOwner)
// return false;
// if (_networkObject.IsServerOnly)
// return false;
// return true;
// }
// /// <summary>
// /// Returns if this transform matches arguments.
// /// </summary>
// /// <returns></returns>
// private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
// {
// bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.position == position));
// bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.rotation == rotation));
// return (positionMatches && rotationMatches);
// }
// /// <summary>
// /// Returns if there is any change between two datas.
// /// </summary>
// private bool HasChanged(TransformPropertiesCls a, TransformPropertiesCls b)
// {
// return (a.Position != b.Position) ||
// (a.Rotation != b.Rotation);
// }
// ///// <summary>
// ///// Returns if the transform differs from td.
// ///// </summary>
// //private bool HasChanged(TransformPropertiesCls tp)
// //{
// // Transform t = _networkObject.transform;
// // bool changed = (tp.Position != t.position) || (tp.Rotation != t.rotation);
// // return changed;
// //}
// /// <summary>
// /// Sets CurrentGoalData to the next in queue. Returns if was set successfully.
// /// </summary>
// private bool SetCurrentGoalData()
// {
// if (_goalDatas.Count == 0)
// {
// _currentGoalData.IsValid = false;
// return false;
// }
// else
// {
// /* Update to the next goal data.
// * We could assign _goalDatas[0] as the
// * current and then just remove it from
// * the collection. But if did that _currentGoalData
// * would have to be disposed first. So all the same,
// * we're using the Update then dispose because it's
// * a little easier to follow. */
// _currentGoalData.Update(_goalDatas[0]);
// Vector3 offset = new Vector3(0f, 10f, 0f);
// Debug.DrawLine(_goalDatas[0].TransformProperties.Position + offset, _goalDatas[0].TransformProperties.Position - offset, Color.red, 2f);
// //Store old and remove it.
// ResettableObjectCaches<GoalData>.Store(_goalDatas[0]);
// //_goalDatas.RemoveRange(true, 1);
// _goalDatas.RemoveRange(0, 1);
// //Debug.LogWarning($"Frame {Time.frameCount}. CurrentGoalData set to Tick {_currentGoalData.DataTick}. PosX/Y {_currentGoalData.TransformProperties.Position.x}, {_currentGoalData.TransformProperties.Position.y}. Rate {_currentGoalData.MoveRates.Position}");
// return true;
// }
// }
// /// <summary>
// /// Moves to a GoalData. Automatically determins if to use data from server or client.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void MoveToTarget(float deltaOverride = -1f)
// {
// /* If the current goal data is not valid then
// * try to set a new one. If none are available
// * it will remain inactive. */
// if (!_currentGoalData.IsValid)
// {
// if (!SetCurrentGoalData())
// return;
// }
// float delta = (deltaOverride != -1f) ? deltaOverride : Time.deltaTime;
// /* Once here it's safe to assume the object will be moving.
// * Any checks which would stop it from moving be it client
// * auth and owner, or server controlled and server, ect,
// * would have already been run. */
// TransformPropertiesCls td = _currentGoalData.TransformProperties;
// RateData rd = _currentGoalData.MoveRates;
// int queueCount = _goalDatas.Count;
// /* Begin moving even if interpolation buffer isn't
// * met to provide more real-time interactions but
// * speed up when buffer is too large. This should
// * provide a good balance of accuracy. */
// float multiplier;
// int countOverInterpolation = (queueCount - (int)_currentInterpolation - (int)_currentInterpolation);
// if (countOverInterpolation > 0)
// {
// float overflowMultiplier = OVERFLOW_MULTIPLIER;
// overflowMultiplier = 0f;
// multiplier = 1f + overflowMultiplier; //(overflowMultiplier * countOverInterpolation);
// }
// else if (countOverInterpolation < 0)
// {
// float value = (UNDERFLOW_MULTIPLIER * Mathf.Abs(countOverInterpolation));
// const float maximum = 0.9f;
// if (value > maximum)
// value = maximum;
// multiplier = 1f - value;
// }
// else
// {
// multiplier = 1f;
// }
// //Rate to update. Changes per property.
// float rate;
// Transform t = _smoothingData.GraphicalObject;
// //Position.
// if (_smoothingData.SmoothPosition)
// {
// rate = rd.Position;
// Vector3 posGoal = td.Position;
// //Debug.Log($"Rate {rate}. PosY {posGoal.y}. Multiplier {multiplier}. QueueCount {queueCount}");
// if (rate == -1f)
// t.position = td.Position;
// else if (rate > 0f)
// t.position = Vector3.MoveTowards(t.position, posGoal, rate * delta * multiplier);
// }
// //Rotation.
// if (_smoothingData.SmoothRotation)
// {
// rate = rd.Rotation;
// if (rate == -1f)
// t.rotation = td.Rotation;
// else if (rate > 0f)
// t.rotation = Quaternion.RotateTowards(t.rotation, td.Rotation, rate * delta);
// }
// //Subtract time remaining for movement to complete.
// if (rd.TimeRemaining > 0f)
// {
// float subtractionAmount = (delta * multiplier);
// float timeRemaining = rd.TimeRemaining - subtractionAmount;
// rd.TimeRemaining = timeRemaining;
// }
// //If movement shoudl be complete.
// if (rd.TimeRemaining <= 0f)
// {
// //_smoothingData.GraphicalObject.transform.position = _currentGoalData.TransformProperties.Position;
// //_smoothingData.GraphicalObject.transform.rotation = _currentGoalData.TransformProperties.Rotation;
// float leftOver = Mathf.Abs(rd.TimeRemaining);
// if (SetCurrentGoalData())
// {
// if (leftOver > 0f)
// MoveToTarget(leftOver);
// }
// //No more in buffer, see if can extrapolate.
// else
// {
// /* Everything should line up when
// * time remaining is <= 0f but incase it's not,
// * such as if the user manipulated the grapihc object
// * somehow, then set goaldata active again to continue
// * moving it until it lines up with the goal. */
// if (!GraphicalObjectMatches(td.Position, td.Rotation))
// _currentGoalData.IsValid = true;
// }
// }
// }
// #region Rates.
// /// <summary>
// /// Sets move rates which will occur instantly.
// /// </summary>
// private void SetInstantRates(RateData rd)
// {
// Debug.LogError($"Instant rates set.");
// rd.Update(MoveRates.INSTANT_VALUE, MoveRates.INSTANT_VALUE, 1, MoveRates.INSTANT_VALUE);
// }
// /// <summary>
// /// Sets move rates which will occur over time.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void SetCalculatedRates(GoalData prevGd, GoalData nextGd, bool datasCleared, Channel channel)
// {
// datasCleared = false;
// /* Only update rates if data has changed.
// * When data comes in reliably for eventual consistency
// * it's possible that it will be the same as the last
// * unreliable packet. When this happens no change has occurred
// * and the distance of change would also be 0; this prevents
// * the object from moving. Only need to compare data if channel is reliable. */
// if (channel == Channel.Reliable && !HasChanged(prevGd.TransformProperties, nextGd.TransformProperties))
// {
// Debug.LogError("Reliable and unchanged.");
// nextGd.MoveRates.Update(prevGd.MoveRates);
// //Set to 0 to indicate settled.
// nextGd.DataTick = 0;
// return;
// }
// uint lastTick = prevGd.DataTick;
// /* How much time has passed between last update and current.
// * If set to 0 then that means the transform has
// * settled. */
// if (lastTick == 0)
// lastTick = (nextGd.DataTick - 1);
// uint tickDifference = (nextGd.DataTick - lastTick);
// float timePassed = (float)_networkObject.TimeManager.TicksToTime(tickDifference);
// RateData nextRd = nextGd.MoveRates;
// float rateMultiplier;
// if (!datasCleared)
// {
// rateMultiplier = 1f;
// }
// else
// {
// float tickDelta = (float)_networkObject.TimeManager.TickDelta;
// rateMultiplier = (_currentGoalData.MoveRates.TimeRemaining / tickDelta);
// }
// //Distance between properties.
// float distance;
// //Position.
// Vector3 lastPosition = prevGd.TransformProperties.Position;
// distance = Vector3.Distance(lastPosition, nextGd.TransformProperties.Position);
// if (tickDifference == 0)
// Debug.LogError($"0 tick difference");
// //If distance teleports assume rest do.
// if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
// {
// SetInstantRates(nextRd);
// return;
// }
// //Position distance already calculated.
// float positionRate = (distance / timePassed);
// if (positionRate <= 0f || positionRate > 5.6f && !_networkObject.IsServer && !_networkObject.IsOwner)
// //Debug.LogError($"Position Rate {positionRate} for tick {nextGd.LocalTick}. PrevY {prevGd.TransformProperties.Position.y}. NextY {nextGd.TransformProperties.Position.y}");
// //Rotation.
// distance = prevGd.TransformProperties.Rotation.Angle(nextGd.TransformProperties.Rotation, true);
// float rotationRate = (distance / timePassed);
// //if (positionRate > 5.1f || positionRate <= 0.05f)
// //Debug.Log($"Rate {positionRate}. Distance {distance}. TickDifference {tickDifference}.");
// /* If no speed then snap just in case.
// * 0f could be from floating errors. */
// if (positionRate == 0f)
// positionRate = MoveRates.INSTANT_VALUE;
// if (rotationRate == 0f)
// rotationRate = MoveRates.INSTANT_VALUE;
// nextRd.Update(positionRate * rateMultiplier, rotationRate * rateMultiplier, tickDifference, timePassed);
// }
// #endregion
// /// <summary>
// /// Removes GoalDatas which make the queue excessive.
// /// This could cause teleportation but would rarely occur, only potentially during sever network issues.
// /// </summary>
// private void RemoveExcessiveGoalDatas()
// {
// if (_goalDatas.Count > 100)
// Debug.LogError($"Whoa getting kind of high with count of {_goalDatas.Count}");
// ///* Remove entries which are excessive to the buffer.
// //* This could create a starting jitter but it will ensure
// //* the buffer does not fill too much. The buffer next should
// //* actually get unreasonably high but rather safe than sorry. */
// //int maximumBufferAllowance = ((int)_currentInterpolation * 8);
// //int removedBufferCount = (_goalDatas.Count - maximumBufferAllowance);
// ////If there are some to remove.
// //if (removedBufferCount > 0)
// //{
// // for (int i = 0; i < removedBufferCount; i++)
// // ResettableObjectCaches<GoalData>.Store(_goalDatas[0 + i]);
// // //_goalDatas.RemoveRange(true, removedBufferCount);
// // _goalDatas.RemoveRange(0, removedBufferCount);
// //}
// }
// /// <summary>
// /// Returns if a tick is older than or equal to the current GoalData and outputs current GoalData tick.
// /// </summary>
// private bool OldGoalDataTick(uint tick, out uint currentGoalDataTick)
// {
// currentGoalDataTick = _currentGoalData.DataTick;
// return (tick <= currentGoalDataTick);
// }
// /// <summary>
// /// Creates the next GoalData using previous goalData and tick.
// /// </summary>
// /// <param name="tick">Tick to apply for the next goal data.</param>
// private GoalData CreateNextGoalData(uint tick, GoalData prevGoalData, bool datasCleared)
// {
// //Debug.Log($"Creating next GoalData for tick {tick}. PrevGoalData tick {prevGoalData.LocalTick}");
// //Begin building next goal data.
// GoalData nextGoalData = ResettableObjectCaches<GoalData>.Retrieve();
// nextGoalData.DataTick = tick;
// //Set next transform data.
// TransformPropertiesCls nextTp = nextGoalData.TransformProperties;
// nextTp.Update(_networkObject.transform);
// /* Reset properties if smoothing is not enabled
// * for them. It's less checks and easier to do it
// * after the nextGoalData is populated. */
// if (!_smoothingData.SmoothPosition)
// nextTp.Position = _graphicalPretickValues.Position;
// if (!_smoothingData.SmoothRotation)
// nextTp.Rotation = _graphicalPretickValues.Rotation;
// // Debug.Log($"Creating NextGd X {nextTp.Position.x} for tick {tick}.");
// //Calculate rates for prev vs next data.
// SetCalculatedRates(prevGoalData, nextGoalData, datasCleared, Channel.Unreliable);
// return nextGoalData;
// }
// /// <summary>
// /// Makes a GoalData using transform values from rootPostSimulateOffsets.
// /// </summary>
// /// <returns></returns>
// private GoalData CreateGoalDataFromRootPreSimulate(uint tick)
// {
// GoalData gd = ResettableObjectCaches<GoalData>.Retrieve();
// //RigidbodyData contains the data from preTick.
// // Debug.Log($"Creating goalData from X {_rootPostSimulateValues.Position.x}. Tick {tick}");
// gd.TransformProperties.Update(_rootPreSimulateValues);
// gd.DataTick = tick;
// //No need to update rates because this is just a starting point reference for interpolation.
// return gd;
// }
// /// <summary>
// /// Clears all goalDatas.
// /// </summary>
// private void ClearGoalData(bool clearCurrent)
// {
// if (clearCurrent)
// ResettableObjectCaches<GoalData>.Store(_currentGoalData);
// int count = _goalDatas.Count;
// for (int i = 0; i < count; i++)
// ResettableObjectCaches<GoalData>.Store(_goalDatas[i]);
// _goalDatas.Clear();
// }
// private uint _jumpTick;
// private uint _lastPostTickDataTick;
// /// <summary>
// /// Creates a GoalData after a simulate.
// /// </summary>
// /// <param name="postTick">True if being created for OnPostTick.</param>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void CreatePostSimulateGoalData(uint tick, bool postTick)
// {
// bool jumping = (_networkObject.transform.position.y > 0.5f);
// bool dataCleared = false;
// int dataIndex = -1;
// bool useUpdate = false;
// RemoveExcessiveGoalDatas();
// if (tick <= _currentGoalData.DataTick)
// {
// if (!postTick)
// {
// if (jumping)
// Debug.LogWarning($"Frame {Time.frameCount}. Old tick. Tick {tick}. Current {_currentGoalData.DataTick}. QueueCount {_goalDatas.Count}");
// return;
// }
// else
// {
// dataCleared = true;
// ClearGoalData(false);
// Debug.LogWarning($"Frame {Time.frameCount}. Tick {tick}. Current {_currentGoalData.DataTick}. CLEARING!");
// }
// }
// uint prevArrTick = 0;
// for (int i = 0; i < _goalDatas.Count; i++)
// {
// uint arrTick = _goalDatas[i].DataTick;
// if (tick == arrTick)
// {
// dataIndex = i;
// useUpdate = true;
// break;
// }
// else if (i > 0 && tick > prevArrTick && tick < arrTick)
// {
// dataIndex = i;
// break;
// }
// prevArrTick = arrTick;
// }
// if (dataIndex == -1)
// {
// if (_goalDatas.Count > 0 && tick < _goalDatas[0].DataTick)
// {
// // Insert at the beginning.
// dataIndex = 0;
// }
// else
// {
// // Insert at the end.
// dataIndex = _goalDatas.Count;
// }
// }
// GoalData prevGd;
// if (dataCleared)
// {
// prevGd = ResettableObjectCaches<GoalData>.Retrieve();
// prevGd.Update(_currentGoalData);
// prevGd.DataTick = (tick - 1);
// }
// else
// {
// prevGd = CreateGoalDataFromRootPreSimulate(tick - 1);
// }
// GoalData nextGd = CreateNextGoalData(tick, prevGd, dataCleared);
// if (jumping)
// {
// Debug.Log($"Frame {Time.frameCount}. CreateGoalData. Tick {tick}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}");
// SceneLoadData sld = new SceneLoadData(_networkObject.gameObject.scene);
// _jumpTick = nextGd.DataTick;
// }
// if (useUpdate && _goalDatas[dataIndex].DataTick == _jumpTick)
// {
// Debug.LogError($"Frame {Time.frameCount}. Overwriting jump. Tick {tick}. IndexTick {_goalDatas[dataIndex].DataTick}. CurrentGoalY {_goalDatas[dataIndex].TransformProperties.Position.y}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}.");
// }
// if (useUpdate)
// _goalDatas[dataIndex].Update(nextGd);
// else
// _goalDatas.Insert(dataIndex, nextGd);
// //Debug.
// if (postTick)
// {
// Vector3 offset = new Vector3(0.15f, 4f, 0f);
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.green, 2f);
// }
// else
// {
// Vector3 offset = new Vector3(-0.15f, 4f, 0f);
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.cyan, 2f);
// }
// }
// uint _postTickGdCount;
// #endif
// }
//}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 28460ed39f6c7e44498b2332d72f1aaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,73 @@
using UnityEngine;
namespace FishNet.Object.Prediction
{
/// <summary>
/// How to favor smoothing for predicted objects.
/// </summary>
internal enum AdaptiveSmoothingType
{
/// <summary>
/// Favor accurate collisions. With fast moving objects this may result in some jitter with higher latencies.
/// </summary>
Accuracy = 0,
/// <summary>
/// A mix between Accuracy and Smoothness.
/// </summary>
Mixed = 1,
/// <summary>
/// Prefer smooth movement and corrections. Fast moving objects may collide before the graphical representation catches up.
/// </summary>
Gradual = 2,
/// <summary>
/// Configure values to your preference.
/// </summary>
Custom = 3,
}
[System.Serializable]
public struct AdaptiveInterpolationSmoothingData
{
[HideInInspector, System.NonSerialized]
public bool SmoothPosition;
[HideInInspector, System.NonSerialized]
public bool SmoothRotation;
[HideInInspector, System.NonSerialized]
public bool SmoothScale;
[HideInInspector,System.NonSerialized]
public Transform GraphicalObject;
[HideInInspector,System.NonSerialized]
public NetworkObject NetworkObject;
[HideInInspector, System.NonSerialized]
public float TeleportThreshold;
/// <summary>
/// Percentage of ping to use as interpolation. Higher values will result in more interpolation.
/// </summary>
[Tooltip("Percentage of ping to use as interpolation. Higher values will result in more interpolation.")]
[Range(0.01f, 5f)]
public float InterpolationPercent;
/// <summary>
/// Percentage of ping to use as interpolation when colliding with an object local client owns.
/// This is used to speed up local interpolation when predicted objects collide with a player as well keep graphics closer to the objects root while colliding.
/// </summary>
[Tooltip("Percentage of ping to use as interpolation when colliding with an object local client owns." +
"This is used to speed up local interpolation when predicted objects collide with a player as well keep graphics closer to the objects root while colliding.")]
[Range(0.01f, 5f)]
public float CollisionInterpolationPercent;
/// <summary>
/// How much per tick to decrease to collision interpolation when colliding with a local player object.
/// Higher values will set interpolation to collision settings faster.
/// </summary>
[Tooltip("How much per tick to decrease to collision interpolation when colliding with a local player object. Higher values will set interpolation to collision settings faster.")]
[Range(1, byte.MaxValue)]
public byte InterpolationDecreaseStep;
/// <summary>
/// How much per tick to increase to normal interpolation when not colliding with a local player object.
/// Higher values will set interpolation to normal settings faster.
/// </summary>
[Tooltip("How much per tick to increase to normal interpolation when not colliding with a local player object. Higher values will set interpolation to normal settings faster.")]
[Range(1, byte.MaxValue)]
public byte InterpolationIncreaseStep;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9fcf00526b5e1bf40a6565c3f521d926
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,589 @@
using GameKit.Utilities;
using FishNet.Utility.Extension;
using System.Runtime.CompilerServices;
using UnityEngine;
using System.Collections.Generic;
namespace FishNet.Object.Prediction
{
internal class AdaptiveInterpolationSmootherFixed
{
#if PREDICTION_V2
#region Types.
/// <summary>
/// Data on a goal to move towards.
/// </summary>
private class GoalData : IResettable
{
/// <summary>
/// True if this GoalData is valid.
/// </summary>
public bool IsValid;
/// <summary>
/// Tick of the data this GoalData is for.
/// </summary>
public uint DataTick;
/// <summary>
/// Transform values to move towards.
/// </summary>
public TransformPropertiesCls TransformProperties = new TransformPropertiesCls();
/// <summary>
/// Time remaining to move towards goal.
/// </summary>
public float TimeRemaining;
public GoalData() { }
public void InitializeState() { }
public void ResetState()
{
DataTick = 0;
TimeRemaining = 0f;
TransformProperties.ResetState();
IsValid = false;
}
/// <summary>
/// Updates values using a GoalData.
/// </summary>
public void Update(GoalData gd)
{
DataTick = gd.DataTick;
TransformProperties.Update(gd.TransformProperties);
TimeRemaining = gd.TimeRemaining;
IsValid = true;
}
public void Update(uint dataTick, TransformPropertiesCls tp, float timeRemaining)
{
DataTick = dataTick;
TransformProperties = tp;
TimeRemaining = timeRemaining;
IsValid = true;
}
}
#endregion
#region Private.
/// <summary>
/// Offsets of the root object during PreTick or PreReplicateReplay.
/// </summary>
private TransformProperties _rootPreSimulateWorldValues;
/// <summary>
/// Offsets of the graphical object during PreTick or PreReplicateReplay.
/// </summary>
private TransformProperties _graphicalPreSimulateWorldValues;
/// <summary>
/// SmoothingData to use.
/// </summary>
private AdaptiveInterpolationSmoothingData _smoothingData;
/// <summary>
/// Current interpolation value. This changes based on ping and settings.
/// </summary>
public long _currentInterpolation = 15;
/// <summary>
/// Current GoalData being used.
/// </summary>
private GoalData _currentGoalData = new GoalData();
/// <summary>
/// MoveRates for currentGoalData.
/// </summary>
private MoveRates _currentMoveRates;
/// <summary>
/// GoalDatas to move towards.
/// </summary>
//private RingBuffer<GoalData> _goalDatas = new RingBuffer<GoalData>();
private List<GoalData> _goalDatas = new List<GoalData>();
/// <summary>
/// Cached NetworkObject reference in SmoothingData for performance.
/// </summary>
private NetworkObject _networkObject;
/// <summary>
/// Cached tickDelta on the TimeManager.
/// </summary>
private float _tickDelta;
/// <summary>
/// Multiplier to apply towards movements. This is used to speed up and slow down buffer as needed.
/// </summary>
private float _rateMultiplier = 1f;
#endregion
#region Const.
/// <summary>
/// Multiplier to apply to movement speed when buffer is over interpolation.
/// </summary>
private const float OVERFLOW_MULTIPLIER = 10f;
/// <summary>
/// Multiplier to apply to movement speed when buffer is under interpolation.
/// </summary>
private const float UNDERFLOW_MULTIPLIER = 1f;
#endregion
public AdaptiveInterpolationSmootherFixed()
{
/* Initialize for up to 50
* goal datas. Anything beyond that
* is unreasonable. */
//_goalDatas.Initialize(50);
}
/// <summary>
/// Initializes this for use.
/// </summary>
internal void Initialize(AdaptiveInterpolationSmoothingData data)
{
_smoothingData = data;
_networkObject = data.NetworkObject;
_tickDelta = (float)_networkObject.TimeManager.TickDelta;
SetGraphicalObject(data.GraphicalObject);
}
/// <summary>
/// <summary>
/// Called every frame.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update()
{
if (CanSmooth())
MoveToTarget();
}
/// <summary>
/// Called when the TimeManager invokes OnPreTick.
/// </summary>
public void OnPreTick()
{
if (CanSmooth())
{
_graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties();
_rootPreSimulateWorldValues.Update(_networkObject.transform);
}
}
/// <summary>
/// Called when the TimeManager invokes OnPostTick.
/// </summary>
public void OnPostTick()
{
if (CanSmooth())
{
//Reset graphics to start graphicals transforms properties.
_smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPreSimulateWorldValues.Position, _graphicalPreSimulateWorldValues.Rotation);
//Create a goal data for new transform position.
uint tick = _networkObject.LastUnorderedReplicateTick;
CreatePostSimulateGoalData(tick, true);
}
}
/// <summary>
/// Called before a reconcile runs a replay.
/// </summary>
public void OnPreReplicateReplay(uint clientTick, uint serverTick)
{
//Update the last post simulate data.
if (CanSmooth())
_rootPreSimulateWorldValues.Update(_networkObject.transform);
}
/// <summary>
/// Called after a reconcile runs a replay.
/// </summary>
public void OnPostReplicateReplay(uint clientTick, uint serverTick)
{
if (CanSmooth())
{
/* Create new goal data from the replay.
* This must be done every replay. If a desync
* did occur then the goaldatas would be different
* from what they were previously. */
uint tick = _networkObject.LastUnorderedReplicateTick;
CreatePostSimulateGoalData(tick, false);
}
}
public void OnPostReconcile(uint clientReconcileTick, uint serverReconcileTick)
{
if (CanSmooth())
{
_rootPreSimulateWorldValues.Update(_networkObject.transform);
int countOverInterpolation = (_goalDatas.Count - (int)_currentInterpolation);
//Debug.Log($"{Time.frameCount}. CountOver {countOverInterpolation}");
}
}
/// <summary>
/// Sets GraphicalObject.
/// </summary>
/// <param name="value"></param>
public void SetGraphicalObject(Transform value)
{
_smoothingData.GraphicalObject = value;
_graphicalPreSimulateWorldValues.Update(value);
}
/// <summary>
/// Returns if the graphics can be smoothed.
/// </summary>
/// <returns></returns>
private bool CanSmooth()
{
if (_networkObject.IsOwner)
return false;
if (_networkObject.IsServerOnly || _networkObject.IsHost)
return false;
return true;
}
/// <summary>
/// Returns if this transform matches arguments.
/// </summary>
/// <returns></returns>
private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
{
bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.position == position));
bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.rotation == rotation));
return (positionMatches && rotationMatches);
}
/// <summary>
/// Sets CurrentGoalData to the next in queue. Returns if was set successfully.
/// </summary>
private bool SetCurrentGoalData()
{
if (_goalDatas.Count == 0)
{
_currentGoalData.IsValid = false;
Debug.LogError("No more goal datas.");
return false;
}
else
{
/* Previous will always be current since
* we are getting next in queue. We
* later check if current is valid to determine
* if instant rates should be set or normal rates.
* If current is not valie then instant rates are set
* to teleport graphics to their starting position, and
* future sets will have a valid current. */
GoalData prev = _currentGoalData;
//Set next and make valid.
GoalData next = _goalDatas[0];
//Remove from goalDatas.
_goalDatas.RemoveAt(0);
if (prev != null && prev.IsValid)
SetCurrentMoveRates(prev.DataTick, next.DataTick, prev.TransformProperties, next.TransformProperties);
else
_currentMoveRates.SetInstantRates();
//Store previous.
if (prev != null)
ResettableObjectCaches<GoalData>.Store(prev);
//Assign new current.
_currentGoalData = next;
Debug.LogWarning($"Set CurrentGoalData on tick {_currentGoalData.DataTick}, remaining {_goalDatas.Count}");
return true;
}
}
/// <summary>
/// Moves to a GoalData. Automatically determins if to use data from server or client.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MoveToTarget(float deltaOverride = -1f)
{
/* If the current goal data is not valid then
* try to set a new one. If none are available
* it will remain inactive. */
if (!_currentGoalData.IsValid)
{
if (!SetCurrentGoalData())
return;
}
GoalData currentGd = _currentGoalData;
float delta = (deltaOverride != -1f) ? deltaOverride : Time.deltaTime;
/* Once here it's safe to assume the object will be moving.
* Any checks which would stop it from moving be it client
* auth and owner, or server controlled and server, ect,
* would have already been run. */
TransformPropertiesCls td = currentGd.TransformProperties;
MoveRates mr = _currentMoveRates;
//How much multiplier should change in either direction over a second.
float multiplierChangeRate = 0.3f;
int queueCount = _goalDatas.Count;
/* Begin moving even if interpolation buffer isn't
* met to provide more real-time interactions but
* speed up when buffer is too large. This should
* provide a good balance of accuracy. */
int countOverInterpolation = (queueCount - (int)_currentInterpolation);
string debugPrint = string.Empty;
//Really high over interpolation, snap to datas.
if (countOverInterpolation > (_currentInterpolation * 30))
{
debugPrint = $"OverInterpolation {countOverInterpolation}. Teleporting.";
mr.SetInstantRates();
//Setting to -1 will force it to go negative, which will clear next goal data for teleport as well.
currentGd.TimeRemaining = -1f;
}
else if (countOverInterpolation > 0)
{
debugPrint = $"OverInterpolation {countOverInterpolation}. Increasing.";
_rateMultiplier += (multiplierChangeRate * delta);
}
else if (countOverInterpolation < 0)
{
debugPrint = $"OverInterpolation {countOverInterpolation}. Slowing.";
_rateMultiplier -= (multiplierChangeRate * delta);
}
else
{
_rateMultiplier = Mathf.MoveTowards(_rateMultiplier, 1f, (multiplierChangeRate * delta));
}
//Clamp multiplier.
const float maximumMultiplier = 1.1f;
const float minimumMultiplier = 0.95f;
_rateMultiplier = Mathf.Clamp(_rateMultiplier, minimumMultiplier, maximumMultiplier);
//Apply multiplier to delta.
delta *= _rateMultiplier;
// if (debugPrint != string.Empty && _networkObject.TimeManager.FrameTicked)
// Debug.Log($"{debugPrint}. Multiplier {_rateMultiplier}");
//multiplier = 1f;
//delta = Time.deltaTime;
//Rate to update. Changes per property.
float rate;
Transform t = _smoothingData.GraphicalObject;
//Position.
if (_smoothingData.SmoothPosition)
{
rate = mr.Position;
Vector3 posGoal = td.Position;
if (rate == MoveRatesCls.INSTANT_VALUE)
t.position = td.Position;
else if (rate > 0f)
t.position = Vector3.MoveTowards(t.position, posGoal, rate * delta);
}
//Rotation.
if (_smoothingData.SmoothRotation)
{
rate = mr.Rotation;
if (rate == MoveRatesCls.INSTANT_VALUE)
t.rotation = td.Rotation;
else if (rate > 0f)
t.rotation = Quaternion.RotateTowards(t.rotation, td.Rotation, rate * delta);
}
if (currentGd.TimeRemaining > 0f)
currentGd.TimeRemaining -= delta;
if (currentGd.TimeRemaining <= 0f)
{
bool graphicsMatch = GraphicalObjectMatches(td.Position, td.Rotation);
if (graphicsMatch)
{
float leftOver = Mathf.Abs(currentGd.TimeRemaining);
if (SetCurrentGoalData())
MoveToTarget(leftOver);
}
}
}
#region Rates.
/// <summary>
/// Sets move rates which will occur over time.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetCurrentMoveRates(uint prevTick, uint tick, TransformPropertiesCls prevTp, TransformPropertiesCls nextTp)
{
long lngTicksPassed = (tick - prevTick);
//Should not happen.
if (lngTicksPassed <= 0)
{
_networkObject.NetworkManager.LogError($"Ticks passed returned negative as {lngTicksPassed}. Instant rates are being set.");
_currentMoveRates.SetInstantRates();
return;
}
//More than 1 tick, also unusual.
else if (lngTicksPassed > 1)
{
_networkObject.NetworkManager.LogError($"Ticks passed are not equal to 1, passed value is {lngTicksPassed}");
// lngTicksPassed = 1;
}
uint ticksPassed = (uint)lngTicksPassed;
float delta = _tickDelta;
float distance;
float rate;
const float v3Tolerance = 0.0001f;
const float qTolerance = 0.2f;
//Position.
rate = prevTp.Position.GetRate(nextTp.Position, delta, out distance, ticksPassed);
//If distance teleports assume rest do.
if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
{
Debug.Log($"Teleporting threshhold.");
_currentMoveRates.SetInstantRates();
return;
}
float positionRate = rate.SetIfUnderTolerance(v3Tolerance, MoveRates.INSTANT_VALUE);
//Rotation.
rate = prevTp.Rotation.GetRate(nextTp.Rotation, delta, out _, ticksPassed);
float rotationRate = rate.SetIfUnderTolerance(qTolerance, MoveRates.INSTANT_VALUE);
_currentMoveRates.Update(positionRate, rotationRate, MoveRates.INSTANT_VALUE);
}
#endregion
/// <summary>
/// Removes GoalDatas which make the queue excessive.
/// This could cause teleportation but would rarely occur, only potentially during sever network issues.
/// </summary>
private void RemoveExcessiveGoalDatas()
{
if (_goalDatas.Count > 100)
Debug.LogError($"Whoa getting kind of high with count of {_goalDatas.Count}");
///* Remove entries which are excessive to the buffer.
//* This could create a starting jitter but it will ensure
//* the buffer does not fill too much. The buffer next sho0..uld
//* actually get unreasonably high but rather safe than sorry. */
//int maximumBufferAllowance = ((int)_currentInterpolation * 8);
//int removedBufferCount = (_goalDatas.Count - maximumBufferAllowance);
////If there are some to remove.
//if (removedBufferCount > 0)
//{
// for (int i = 0; i < removedBufferCount; i++)
// ResettableObjectCaches<GoalData>.Store(_goalDatas[0 + i]);
// //_goalDatas.RemoveRange(true, removedBufferCount);
// _goalDatas.RemoveRange(0, removedBufferCount);
//}
}
/// <summary>
/// Creates a GoalData after a simulate.
/// </summary>
/// <param name="postTick">True if being created for OnPostTick.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CreatePostSimulateGoalData(uint tick, bool postTick)
{
RemoveExcessiveGoalDatas();
int dataIndex = -1;
bool useUpdate = false;
/* Post ticks always go on the end.
* The tick will be wrong for the post tick, so set it
* to the last entry tick + 1. */
int datasCount = _goalDatas.Count;
if (postTick)
{
if (datasCount > 0)
tick = _goalDatas[datasCount - 1].DataTick + 1;
else
tick = _currentGoalData.DataTick + 1;
dataIndex = datasCount;
}
else
{
/* There is no need to create a goaldata
* if the tick is previous to currentGoalData.
* This would indicate the graphics have already
* moved past tick. */
if (tick < _currentGoalData.DataTick)
{
//Debug.LogWarning($"Frame {Time.frameCount}. Skipping tick {tick}. Current {_currentGoalData.DataTick}. PostTick? {postTick}. QueueCount {_goalDatas.Count}. StatesCount {_networkObject.PredictionManager._recievedStates.Count}");
return;
}
//If current tick then let current play out and do nothing.
else if (tick == _currentGoalData.DataTick)
{
return;
}
uint prevArrTick = 0;
for (int i = 0; i < datasCount; i++)
{
uint arrTick = _goalDatas[i].DataTick;
if (tick == arrTick)
{
dataIndex = i;
useUpdate = true;
break;
}
else if (i > 0 && tick > prevArrTick && tick < arrTick)
{
dataIndex = i;
break;
}
prevArrTick = arrTick;
}
if (dataIndex == -1)
{
//Insert at beginning.
if (datasCount > 0 && tick < _goalDatas[0].DataTick)
dataIndex = 0;
//Insert at end.
else
dataIndex = datasCount;
}
}
Transform rootT = _networkObject.transform;
//Begin building next goal data.
GoalData nextGd = ResettableObjectCaches<GoalData>.Retrieve();
nextGd.DataTick = tick;
nextGd.TimeRemaining = _tickDelta;
nextGd.IsValid = true;
//Set next transform data.
TransformPropertiesCls nextTp = nextGd.TransformProperties;
//Position.
if (!_smoothingData.SmoothPosition)
nextTp.Position = _graphicalPreSimulateWorldValues.Position;
else
nextTp.Position = rootT.position;
//ROtation.
if (!_smoothingData.SmoothRotation)
nextTp.Rotation = _graphicalPreSimulateWorldValues.Rotation;
else
nextTp.Rotation = rootT.rotation;
//Vector3 lineDist = new Vector3(0f, 3f, 0f);
//if (!postTick)
// Debug.DrawLine(rootT.position + lineDist, rootT.position, Color.red, 2f);
//else
// Debug.DrawLine(rootT.position + lineDist + new Vector3(1f, 0f, 0f), rootT.position, Color.blue, 2f);
if (useUpdate)
_goalDatas[dataIndex].Update(nextGd);
else
_goalDatas.Insert(dataIndex, nextGd);
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8444905b030752941a0f1562fd44a782
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,887 @@
//using GameKit.Utilities; //Remove on 04/01/01
//using FishNet.Transporting;
//using FishNet.Utility.Extension;
//using System.Runtime.CompilerServices;
//using UnityEngine;
//using FishNet.Managing.Timing;
//using System.Collections.Generic;
//using FishNet.Managing.Scened;
//namespace FishNet.Object.Prediction
//{
// internal class AdaptiveInterpolationSmoother
// {
// #if PREDICTION_V2
// #region Types.
// /// <summary>
// /// Data on a goal to move towards.
// /// </summary>
// private class GoalData : IResettable
// {
// /// <summary>
// /// True if this GoalData is valid.
// /// </summary>
// public bool IsValid;
// /// <summary>
// /// Tick of the data this GoalData is for.
// /// </summary>
// public uint DataTick;
// /// <summary>
// /// Data on how fast to move to transform values.
// /// </summary>
// public RateData MoveRates = new RateData();
// /// <summary>
// /// Transform values to move towards.
// /// </summary>
// public TransformPropertiesCls TransformProperties = new TransformPropertiesCls();
// public GoalData() { }
// public void InitializeState() { }
// public void ResetState()
// {
// DataTick = 0;
// TransformProperties.ResetState();
// MoveRates.ResetState();
// IsValid = false;
// }
// /// <summary>
// /// Updates values using a GoalData.
// /// </summary>
// public void Update(GoalData gd)
// {
// DataTick = gd.DataTick;
// MoveRates.Update(gd.MoveRates);
// TransformProperties.Update(gd.TransformProperties);
// IsValid = true;
// }
// public void Update(uint dataTick, RateData rd, TransformPropertiesCls tp)
// {
// DataTick = dataTick;
// MoveRates = rd;
// TransformProperties = tp;
// IsValid = true;
// }
// }
// /// <summary>
// /// How fast to move to values.
// /// </summary>
// private class RateData : IResettable
// {
// /// <summary>
// /// Rate for position after smart calculations.
// /// </summary>
// public float Position;
// /// <summary>
// /// Rate for rotation after smart calculations.
// /// </summary>
// public float Rotation;
// /// <summary>
// /// Number of ticks the rates are calculated for.
// /// If TickSpan is 2 then the rates are calculated under the assumption the transform changed over 2 ticks.
// /// </summary>
// public uint TickSpan;
// /// <summary>
// /// Time remaining until transform is expected to reach it's goal.
// /// </summary>
// internal float TimeRemaining;
// public RateData() { }
// public void InitializeState() { }
// /// <summary>
// /// Resets values for re-use.
// /// </summary>
// public void ResetState()
// {
// Position = 0f;
// Rotation = 0f;
// TickSpan = 0;
// TimeRemaining = 0f;
// }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public void Update(RateData rd)
// {
// Update(rd.Position, rd.Rotation, rd.TickSpan, rd.TimeRemaining);
// }
// /// <summary>
// /// Updates rates.
// /// </summary>
// public void Update(float position, float rotation, uint tickSpan, float timeRemaining)
// {
// Position = position;
// Rotation = rotation;
// TickSpan = tickSpan;
// TimeRemaining = timeRemaining;
// }
// }
// #endregion
// #region Private.
// /// <summary>
// /// Offsets of the graphical object when this was initialized.
// /// </summary>
// private TransformProperties _graphicalInitializedValues;
// /// <summary>
// /// Offsets of the graphical object during PreTick. This could also be the offset PreReplay.
// /// </summary>
// private TransformProperties _graphicalPretickValues;
// /// <summary>
// /// Offsets of the root transform before simulating. This could be PreTick, or PreReplay.
// /// </summary>
// private TransformProperties _rootPreSimulateValues;
// /// <summary>
// /// SmoothingData to use.
// /// </summary>
// private AdaptiveInterpolationSmoothingData _smoothingData;
// /// <summary>
// /// Current interpolation value. This changes based on ping and settings.
// /// </summary>
// private long _currentInterpolation = 2;
// /// <summary>
// /// Target interpolation when collision is exited. This changes based on ping and settings.
// /// </summary>
// private uint _targetInterpolation;
// /// <summary>
// /// Target interpolation when collision is entered. This changes based on ping and settings.
// /// </summary>
// private uint _targetCollisionInterpolation;
// /// <summary>
// /// Current GoalData being used.
// /// </summary>
// private GoalData _currentGoalData = new GoalData();
// /// <summary>
// /// GoalDatas to move towards.
// /// </summary>
// //private RingBuffer<GoalData> _goalDatas = new RingBuffer<GoalData>();
// private List<GoalData> _goalDatas = new List<GoalData>();
// /// <summary>
// /// Last ping value when it was checked.
// /// </summary>
// private long _lastPing = long.MinValue;
// /// <summary>
// /// Cached NetworkObject reference in SmoothingData for performance.
// /// </summary>
// private NetworkObject _networkObject;
// #endregion
// #region Const.
// /// <summary>
// /// Multiplier to apply to movement speed when buffer is over interpolation.
// /// </summary>
// private const float OVERFLOW_MULTIPLIER = 0.1f;
// /// <summary>
// /// Multiplier to apply to movement speed when buffer is under interpolation.
// /// </summary>
// private const float UNDERFLOW_MULTIPLIER = 0.02f;
// #endregion
// public AdaptiveInterpolationSmoother()
// {
// /* Initialize for up to 50
// * goal datas. Anything beyond that
// * is unreasonable. */
// //_goalDatas.Initialize(50);
// }
// /// <summary>
// /// Initializes this for use.
// /// </summary>
// internal void Initialize(AdaptiveInterpolationSmoothingData data)
// {
// _smoothingData = data;
// _networkObject = data.NetworkObject;
// SetGraphicalObject(data.GraphicalObject);
// }
// /// <summary>
// /// <summary>
// /// Called every frame.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public void Update()
// {
// if (CanSmooth())
// MoveToTarget();
// }
// private string GetGoalDataTicks()
// {
// string result = string.Empty;
// foreach (var item in _goalDatas)
// result += item.DataTick + ", ";
// return result;
// }
// private string _preTickGoalDatas = string.Empty;
// /// <summary>
// /// Called when the TimeManager invokes OnPreTick.
// /// </summary>
// public void OnPreTick()
// {
// _preTickGoalDatas = GetGoalDataTicks();
// if (CanSmooth())
// {
// UpdatePingInterpolation();
// _graphicalPretickValues.Update(_smoothingData.GraphicalObject);
// //Update the last post simulate data.
// UpdateRootPreSimulateOffsets();
// //UpdateRootPreSimulateOffsets(_networkObject.ReplicateTick.Value(_networkObject.TimeManager) - 1);
// }
// }
// /// <summary>
// /// Called when the TimeManager invokes OnPostTick.
// /// </summary>
// public void OnPostTick()
// {
// if (CanSmooth())
// {
// //Move towards target interpolation.
// UpdateCurrentInterpolation();
// //Reset graphics to start graphicals transforms properties.
// _smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPretickValues.Position, _graphicalPretickValues.Rotation);
// //Create a goal data for new transform position.
// uint tick = _networkObject.LastUnorderedReplicateTick;
// //Debug.Log($"GoalDatas count {_goalDatas.Count}. Tick {tick}. LocalTick {_networkObject.TimeManager.LocalTick}");
// CreatePostSimulateGoalData(tick, true);
// }
// }
// /// <summary>
// /// Called before a reconcile runs a replay.
// /// </summary>
// public void OnPreReplicateReplay(uint clientTick, uint serverTick)
// {
// //Update the last post simulate data.
// if (CanSmooth())
// {
// UpdateRootPreSimulateOffsets();
// }
// }
// /// <summary>
// /// Called after a reconcile runs a replay.
// /// </summary>
// public void OnPostReplicateReplay(uint clientTick, uint serverTick)
// {
// if (CanSmooth())
// {
// /* Create new goal data from the replay.
// * This must be done every replay. If a desync
// * did occur then the goaldatas would be different
// * from what they were previously. */
// uint tick = _networkObject.LastUnorderedReplicateTick;
// CreatePostSimulateGoalData(tick, false);
// }
// }
// /// <summary>
// /// Updates rootPostSimulateOffsets value with root transform's current values.
// /// </summary>
// private void UpdateRootPreSimulateOffsets()
// {
// Transform t = _networkObject.transform;
// _rootPreSimulateValues.Update(t.position, t.rotation);
// }
// /// <summary>
// /// Moves current interpolation to target interpolation.
// /// </summary>
// private void UpdateCurrentInterpolation()
// {
// AdaptiveInterpolationSmoothingData data = _smoothingData;
// bool colliding = _networkObject.CollidingWithLocalClient();
// if (colliding)
// _currentInterpolation -= data.InterpolationDecreaseStep;
// else
// _currentInterpolation += data.InterpolationIncreaseStep;
// _currentInterpolation = (long)Mathf.Clamp(_currentInterpolation, _targetCollisionInterpolation, _targetInterpolation);
// }
// /// <summary>
// /// Updates interpolation values based on ping.
// /// </summary>
// private void UpdatePingInterpolation()
// {
// /* Only update if ping has changed considerably.
// * This will prevent random lag spikes from throwing
// * off the interpolation. */
// long ping = _networkObject.TimeManager.RoundTripTime;
// ulong difference = (ulong)Mathf.Abs(ping - _lastPing);
// _lastPing = ping;
// //Allow update if ping jump is large enough.
// if (difference > 25)
// SetTargetSmoothing(ping, false);
// }
// /// <summary>
// /// Sets target smoothing values.
// /// </summary>
// /// <param name="setImmediately">True to set current values to targets immediately.</param>
// private void SetTargetSmoothing(long ping, bool setImmediately)
// {
// AdaptiveInterpolationSmoothingData data = _smoothingData;
// TimeManager tm = _networkObject.TimeManager;
// double interpolationTime = (ping / 1000d) * data.InterpolationPercent;
// _targetInterpolation = tm.TimeToTicks(interpolationTime, TickRounding.RoundUp);
// double collisionInterpolationTime = (ping / 1000d) * data.CollisionInterpolationPercent;
// _targetCollisionInterpolation = tm.TimeToTicks(collisionInterpolationTime, TickRounding.RoundUp);
// //If to apply values to targets immediately.
// if (setImmediately)
// _currentInterpolation = (_networkObject.CollidingWithLocalClient()) ? _targetCollisionInterpolation : _targetInterpolation;
// }
// /// <summary>
// /// Sets GraphicalObject.
// /// </summary>
// /// <param name="value"></param>
// public void SetGraphicalObject(Transform value)
// {
// _smoothingData.GraphicalObject = value;
// _graphicalInitializedValues = _networkObject.transform.GetTransformOffsets(_smoothingData.GraphicalObject);
// }
// /// <summary>
// /// Returns if the graphics can be smoothed.
// /// </summary>
// /// <returns></returns>
// private bool CanSmooth()
// {
// if (_networkObject.IsOwner)
// return false;
// if (_networkObject.IsServerOnly)
// return false;
// return true;
// }
// /// <summary>
// /// Returns if this transform matches arguments.
// /// </summary>
// /// <returns></returns>
// private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
// {
// bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.position == position));
// bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.rotation == rotation));
// return (positionMatches && rotationMatches);
// }
// /// <summary>
// /// Returns if there is any change between two datas.
// /// </summary>
// private bool HasChanged(TransformPropertiesCls a, TransformPropertiesCls b)
// {
// return (a.Position != b.Position) ||
// (a.Rotation != b.Rotation);
// }
// ///// <summary>
// ///// Returns if the transform differs from td.
// ///// </summary>
// //private bool HasChanged(TransformPropertiesCls tp)
// //{
// // Transform t = _networkObject.transform;
// // bool changed = (tp.Position != t.position) || (tp.Rotation != t.rotation);
// // return changed;
// //}
// /// <summary>
// /// Sets CurrentGoalData to the next in queue. Returns if was set successfully.
// /// </summary>
// private bool SetCurrentGoalData()
// {
// if (_goalDatas.Count == 0)
// {
// _currentGoalData.IsValid = false;
// return false;
// }
// else
// {
// /* Update to the next goal data.
// * We could assign _goalDatas[0] as the
// * current and then just remove it from
// * the collection. But if did that _currentGoalData
// * would have to be disposed first. So all the same,
// * we're using the Update then dispose because it's
// * a little easier to follow. */
// _currentGoalData.Update(_goalDatas[0]);
// Vector3 offset = new Vector3(0f, 10f, 0f);
// Debug.DrawLine(_goalDatas[0].TransformProperties.Position + offset, _goalDatas[0].TransformProperties.Position - offset, Color.red, 2f);
// //Store old and remove it.
// ResettableObjectCaches<GoalData>.Store(_goalDatas[0]);
// //_goalDatas.RemoveRange(true, 1);
// _goalDatas.RemoveRange(0, 1);
// //Debug.LogWarning($"Frame {Time.frameCount}. CurrentGoalData set to Tick {_currentGoalData.DataTick}. PosX/Y {_currentGoalData.TransformProperties.Position.x}, {_currentGoalData.TransformProperties.Position.y}. Rate {_currentGoalData.MoveRates.Position}");
// return true;
// }
// }
// /// <summary>
// /// Moves to a GoalData. Automatically determins if to use data from server or client.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void MoveToTarget(float deltaOverride = -1f)
// {
// /* If the current goal data is not valid then
// * try to set a new one. If none are available
// * it will remain inactive. */
// if (!_currentGoalData.IsValid)
// {
// if (!SetCurrentGoalData())
// return;
// }
// float delta = (deltaOverride != -1f) ? deltaOverride : Time.deltaTime;
// /* Once here it's safe to assume the object will be moving.
// * Any checks which would stop it from moving be it client
// * auth and owner, or server controlled and server, ect,
// * would have already been run. */
// TransformPropertiesCls td = _currentGoalData.TransformProperties;
// RateData rd = _currentGoalData.MoveRates;
// int queueCount = _goalDatas.Count;
// /* Begin moving even if interpolation buffer isn't
// * met to provide more real-time interactions but
// * speed up when buffer is too large. This should
// * provide a good balance of accuracy. */
// float multiplier;
// int countOverInterpolation = (queueCount - (int)_currentInterpolation - (int)_currentInterpolation);
// if (countOverInterpolation > 0)
// {
// float overflowMultiplier = OVERFLOW_MULTIPLIER;
// overflowMultiplier = 0f;
// multiplier = 1f + overflowMultiplier; //(overflowMultiplier * countOverInterpolation);
// }
// else if (countOverInterpolation < 0)
// {
// float value = (UNDERFLOW_MULTIPLIER * Mathf.Abs(countOverInterpolation));
// const float maximum = 0.9f;
// if (value > maximum)
// value = maximum;
// multiplier = 1f - value;
// }
// else
// {
// multiplier = 1f;
// }
// //Rate to update. Changes per property.
// float rate;
// Transform t = _smoothingData.GraphicalObject;
// //Position.
// if (_smoothingData.SmoothPosition)
// {
// rate = rd.Position;
// Vector3 posGoal = td.Position;
// //Debug.Log($"Rate {rate}. PosY {posGoal.y}. Multiplier {multiplier}. QueueCount {queueCount}");
// if (rate == -1f)
// t.position = td.Position;
// else if (rate > 0f)
// t.position = Vector3.MoveTowards(t.position, posGoal, rate * delta * multiplier);
// }
// //Rotation.
// if (_smoothingData.SmoothRotation)
// {
// rate = rd.Rotation;
// if (rate == -1f)
// t.rotation = td.Rotation;
// else if (rate > 0f)
// t.rotation = Quaternion.RotateTowards(t.rotation, td.Rotation, rate * delta);
// }
// //Subtract time remaining for movement to complete.
// if (rd.TimeRemaining > 0f)
// {
// float subtractionAmount = (delta * multiplier);
// float timeRemaining = rd.TimeRemaining - subtractionAmount;
// rd.TimeRemaining = timeRemaining;
// }
// //If movement shoudl be complete.
// if (rd.TimeRemaining <= 0f)
// {
// //_smoothingData.GraphicalObject.transform.position = _currentGoalData.TransformProperties.Position;
// //_smoothingData.GraphicalObject.transform.rotation = _currentGoalData.TransformProperties.Rotation;
// float leftOver = Mathf.Abs(rd.TimeRemaining);
// if (SetCurrentGoalData())
// {
// if (leftOver > 0f)
// MoveToTarget(leftOver);
// }
// //No more in buffer, see if can extrapolate.
// else
// {
// /* Everything should line up when
// * time remaining is <= 0f but incase it's not,
// * such as if the user manipulated the grapihc object
// * somehow, then set goaldata active again to continue
// * moving it until it lines up with the goal. */
// if (!GraphicalObjectMatches(td.Position, td.Rotation))
// _currentGoalData.IsValid = true;
// }
// }
// }
// #region Rates.
// /// <summary>
// /// Sets move rates which will occur instantly.
// /// </summary>
// private void SetInstantRates(RateData rd)
// {
// Debug.LogError($"Instant rates set.");
// rd.Update(MoveRates.INSTANT_VALUE, MoveRates.INSTANT_VALUE, 1, MoveRates.INSTANT_VALUE);
// }
// /// <summary>
// /// Sets move rates which will occur over time.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void SetCalculatedRates(GoalData prevGd, GoalData nextGd, bool datasCleared, Channel channel)
// {
// datasCleared = false;
// /* Only update rates if data has changed.
// * When data comes in reliably for eventual consistency
// * it's possible that it will be the same as the last
// * unreliable packet. When this happens no change has occurred
// * and the distance of change would also be 0; this prevents
// * the object from moving. Only need to compare data if channel is reliable. */
// if (channel == Channel.Reliable && !HasChanged(prevGd.TransformProperties, nextGd.TransformProperties))
// {
// Debug.LogError("Reliable and unchanged.");
// nextGd.MoveRates.Update(prevGd.MoveRates);
// //Set to 0 to indicate settled.
// nextGd.DataTick = 0;
// return;
// }
// uint lastTick = prevGd.DataTick;
// /* How much time has passed between last update and current.
// * If set to 0 then that means the transform has
// * settled. */
// if (lastTick == 0)
// lastTick = (nextGd.DataTick - 1);
// uint tickDifference = (nextGd.DataTick - lastTick);
// float timePassed = (float)_networkObject.TimeManager.TicksToTime(tickDifference);
// RateData nextRd = nextGd.MoveRates;
// float rateMultiplier;
// if (!datasCleared)
// {
// rateMultiplier = 1f;
// }
// else
// {
// float tickDelta = (float)_networkObject.TimeManager.TickDelta;
// rateMultiplier = (_currentGoalData.MoveRates.TimeRemaining / tickDelta);
// }
// //Distance between properties.
// float distance;
// //Position.
// Vector3 lastPosition = prevGd.TransformProperties.Position;
// distance = Vector3.Distance(lastPosition, nextGd.TransformProperties.Position);
// if (tickDifference == 0)
// Debug.LogError($"0 tick difference");
// //If distance teleports assume rest do.
// if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
// {
// SetInstantRates(nextRd);
// return;
// }
// //Position distance already calculated.
// float positionRate = (distance / timePassed);
// if (positionRate <= 0f || positionRate > 5.6f && !_networkObject.IsServer && !_networkObject.IsOwner)
// //Debug.LogError($"Position Rate {positionRate} for tick {nextGd.LocalTick}. PrevY {prevGd.TransformProperties.Position.y}. NextY {nextGd.TransformProperties.Position.y}");
// //Rotation.
// distance = prevGd.TransformProperties.Rotation.Angle(nextGd.TransformProperties.Rotation, true);
// float rotationRate = (distance / timePassed);
// //if (positionRate > 5.1f || positionRate <= 0.05f)
// //Debug.Log($"Rate {positionRate}. Distance {distance}. TickDifference {tickDifference}.");
// /* If no speed then snap just in case.
// * 0f could be from floating errors. */
// if (positionRate == 0f)
// positionRate = MoveRates.INSTANT_VALUE;
// if (rotationRate == 0f)
// rotationRate = MoveRates.INSTANT_VALUE;
// nextRd.Update(positionRate * rateMultiplier, rotationRate * rateMultiplier, tickDifference, timePassed);
// }
// #endregion
// /// <summary>
// /// Removes GoalDatas which make the queue excessive.
// /// This could cause teleportation but would rarely occur, only potentially during sever network issues.
// /// </summary>
// private void RemoveExcessiveGoalDatas()
// {
// if (_goalDatas.Count > 100)
// Debug.LogError($"Whoa getting kind of high with count of {_goalDatas.Count}");
// ///* Remove entries which are excessive to the buffer.
// //* This could create a starting jitter but it will ensure
// //* the buffer does not fill too much. The buffer next should
// //* actually get unreasonably high but rather safe than sorry. */
// //int maximumBufferAllowance = ((int)_currentInterpolation * 8);
// //int removedBufferCount = (_goalDatas.Count - maximumBufferAllowance);
// ////If there are some to remove.
// //if (removedBufferCount > 0)
// //{
// // for (int i = 0; i < removedBufferCount; i++)
// // ResettableObjectCaches<GoalData>.Store(_goalDatas[0 + i]);
// // //_goalDatas.RemoveRange(true, removedBufferCount);
// // _goalDatas.RemoveRange(0, removedBufferCount);
// //}
// }
// /// <summary>
// /// Returns if a tick is older than or equal to the current GoalData and outputs current GoalData tick.
// /// </summary>
// private bool OldGoalDataTick(uint tick, out uint currentGoalDataTick)
// {
// currentGoalDataTick = _currentGoalData.DataTick;
// return (tick <= currentGoalDataTick);
// }
// /// <summary>
// /// Creates the next GoalData using previous goalData and tick.
// /// </summary>
// /// <param name="tick">Tick to apply for the next goal data.</param>
// private GoalData CreateNextGoalData(uint tick, GoalData prevGoalData, bool datasCleared)
// {
// //Debug.Log($"Creating next GoalData for tick {tick}. PrevGoalData tick {prevGoalData.LocalTick}");
// //Begin building next goal data.
// GoalData nextGoalData = ResettableObjectCaches<GoalData>.Retrieve();
// nextGoalData.DataTick = tick;
// //Set next transform data.
// TransformPropertiesCls nextTp = nextGoalData.TransformProperties;
// nextTp.Update(_networkObject.transform);
// /* Reset properties if smoothing is not enabled
// * for them. It's less checks and easier to do it
// * after the nextGoalData is populated. */
// if (!_smoothingData.SmoothPosition)
// nextTp.Position = _graphicalPretickValues.Position;
// if (!_smoothingData.SmoothRotation)
// nextTp.Rotation = _graphicalPretickValues.Rotation;
// // Debug.Log($"Creating NextGd X {nextTp.Position.x} for tick {tick}.");
// //Calculate rates for prev vs next data.
// SetCalculatedRates(prevGoalData, nextGoalData, datasCleared, Channel.Unreliable);
// return nextGoalData;
// }
// /// <summary>
// /// Makes a GoalData using transform values from rootPostSimulateOffsets.
// /// </summary>
// /// <returns></returns>
// private GoalData CreateGoalDataFromRootPreSimulate(uint tick)
// {
// GoalData gd = ResettableObjectCaches<GoalData>.Retrieve();
// //RigidbodyData contains the data from preTick.
// // Debug.Log($"Creating goalData from X {_rootPostSimulateValues.Position.x}. Tick {tick}");
// gd.TransformProperties.Update(_rootPreSimulateValues);
// gd.DataTick = tick;
// //No need to update rates because this is just a starting point reference for interpolation.
// return gd;
// }
// /// <summary>
// /// Clears all goalDatas.
// /// </summary>
// private void ClearGoalData(bool clearCurrent)
// {
// if (clearCurrent)
// ResettableObjectCaches<GoalData>.Store(_currentGoalData);
// int count = _goalDatas.Count;
// for (int i = 0; i < count; i++)
// ResettableObjectCaches<GoalData>.Store(_goalDatas[i]);
// _goalDatas.Clear();
// }
// private uint _jumpTick;
// private uint _lastPostTickDataTick;
// /// <summary>
// /// Creates a GoalData after a simulate.
// /// </summary>
// /// <param name="postTick">True if being created for OnPostTick.</param>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void CreatePostSimulateGoalData(uint tick, bool postTick)
// {
// bool jumping = (_networkObject.transform.position.y > 0.5f);
// bool dataCleared = false;
// int dataIndex = -1;
// bool useUpdate = false;
// RemoveExcessiveGoalDatas();
// if (tick <= _currentGoalData.DataTick)
// {
// if (!postTick)
// {
// if (jumping)
// Debug.LogWarning($"Frame {Time.frameCount}. Old tick. Tick {tick}. Current {_currentGoalData.DataTick}. QueueCount {_goalDatas.Count}");
// return;
// }
// else
// {
// dataCleared = true;
// ClearGoalData(false);
// Debug.LogWarning($"Frame {Time.frameCount}. Tick {tick}. Current {_currentGoalData.DataTick}. CLEARING!");
// }
// }
// uint prevArrTick = 0;
// for (int i = 0; i < _goalDatas.Count; i++)
// {
// uint arrTick = _goalDatas[i].DataTick;
// if (tick == arrTick)
// {
// dataIndex = i;
// useUpdate = true;
// break;
// }
// else if (i > 0 && tick > prevArrTick && tick < arrTick)
// {
// dataIndex = i;
// break;
// }
// prevArrTick = arrTick;
// }
// if (dataIndex == -1)
// {
// if (_goalDatas.Count > 0 && tick < _goalDatas[0].DataTick)
// {
// // Insert at the beginning.
// dataIndex = 0;
// }
// else
// {
// // Insert at the end.
// dataIndex = _goalDatas.Count;
// }
// }
// GoalData prevGd;
// if (dataCleared)
// {
// prevGd = ResettableObjectCaches<GoalData>.Retrieve();
// prevGd.Update(_currentGoalData);
// prevGd.DataTick = (tick - 1);
// }
// else
// {
// prevGd = CreateGoalDataFromRootPreSimulate(tick - 1);
// }
// GoalData nextGd = CreateNextGoalData(tick, prevGd, dataCleared);
// if (jumping)
// {
// Debug.Log($"Frame {Time.frameCount}. CreateGoalData. Tick {tick}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}");
// SceneLoadData sld = new SceneLoadData(_networkObject.gameObject.scene);
// _jumpTick = nextGd.DataTick;
// }
// if (useUpdate && _goalDatas[dataIndex].DataTick == _jumpTick)
// {
// Debug.LogError($"Frame {Time.frameCount}. Overwriting jump. Tick {tick}. IndexTick {_goalDatas[dataIndex].DataTick}. CurrentGoalY {_goalDatas[dataIndex].TransformProperties.Position.y}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}.");
// }
// if (useUpdate)
// _goalDatas[dataIndex].Update(nextGd);
// else
// _goalDatas.Insert(dataIndex, nextGd);
// //Debug.
// if (postTick)
// {
// Vector3 offset = new Vector3(0.15f, 4f, 0f);
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.green, 2f);
// }
// else
// {
// Vector3 offset = new Vector3(-0.15f, 4f, 0f);
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.cyan, 2f);
// }
// }
// uint _postTickGdCount;
// #endif
// }
//}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10df9daae891dda488c9fa8715b68556
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
using System;
namespace FishNet.Object.Prediction
{
/// <summary>
/// Replicated methods are to be called from clients and will run the same data and logic on the server.
/// Only data used as method arguments will be serialized.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class ReplicateAttribute : Attribute
{
/// <summary>
/// How many past datas to resend.
/// </summary>
[Obsolete("Use PredictionManager.RedundancyCount.")] //Remove on 2023/06/01
public byte Resends = 5;
/// <summary>
/// True to allow running input passed in with asServer true when there is no owner.
/// </summary>
public bool AllowServerControl = false;
}
/// <summary>
/// Reconcile methods indicate how to reset your script or object after the server has replicated user data.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class ReconcileAttribute : Attribute
{
/// <summary>
/// How many times to resend reconcile.
/// </summary>
[Obsolete("Use PredictionManager.RedundancyCount.")] //Remove on 2023/06/01
public byte Resends = 3;
}
/// <summary>
/// Replicated methods are to be called from clients and will run the same data and logic on the server.
/// Only data used as method arguments will be serialized.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class ReplicateV2Attribute : Attribute { }
/// <summary>
/// Reconcile methods indicate how to reset your script or object after the server has replicated user data.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class ReconcileV2Attribute : Attribute { }
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b082d36535ce0404d8438bc1b0499e53
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
using FishNet.Connection;
using FishNet.Documenting;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility.Constant;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
namespace FishNet.Object.Prediction.Delegating
{
[APIExclude]
public delegate void ReplicateRpcDelegate(PooledReader reader, NetworkConnection sender, Channel channel);
[APIExclude]
public delegate void ReconcileRpcDelegate(PooledReader reader, Channel channel);
#if !PREDICTION_V2
[APIExclude]
public delegate void ReplicateUserLogicDelegate<T>(T data, bool asServer, Channel channel, bool replaying);
[APIExclude]
public delegate void ReconcileUserLogicDelegate<T>(T data, bool asServer, Channel channel);
#else
[APIExclude]
public delegate void ReplicateUserLogicDelegate<T>(T data, ReplicateState state, Channel channel);
[APIExclude]
public delegate void ReconcileUserLogicDelegate<T>(T data, Channel channel);
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9904192dacd41a4ba7b29bc3199ec3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@

namespace FishNet.Object.Prediction
{
public interface IReplicateData
{
/// <summary>
/// Local tick when the data was created.
/// </summary>
/// <returns></returns>
uint GetTick();
/// <summary>
/// Sets the local tick when data was created.
/// </summary>
/// <param name="value"></param>
void SetTick(uint value);
/// <summary>
/// Allows for any cleanup when the data is being discarded.
/// </summary>
void Dispose();
}
public interface IReconcileData
{
/// <summary>
/// Local tick when the data was created.
/// </summary>
/// <returns></returns>
uint GetTick();
/// <summary>
/// Sets the local tick when data was created.
/// </summary>
/// <param name="value"></param>
void SetTick(uint value);
/// <summary>
/// Allows for any cleanup when the data is being discarded.
/// </summary>
void Dispose();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 515754257f85574438408c7f5b268590
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,232 @@

using GameKit.Utilities;
using System.Runtime.CompilerServices;
namespace FishNet.Object.Prediction
{
/// <summary>
/// Data to be used to configure smoothing for an owned predicted object.
/// </summary>
internal struct MoveRates
{
public float Position;
public float Rotation;
public float Scale;
public MoveRates(float value)
{
Position = value;
Rotation = value;
Scale = value;
}
public MoveRates(float position, float rotation)
{
Position = position;
Rotation = rotation;
Scale = INSTANT_VALUE;
}
public MoveRates(float position, float rotation, float scale)
{
Position = position;
Rotation = rotation;
Scale = scale;
}
/// <summary>
/// True if a positional move rate is set.
/// </summary>
public bool PositionSet => (Position != UNSET_VALUE);
/// <summary>
/// True if rotation move rate is set.
/// </summary>
public bool RotationSet => (Rotation != UNSET_VALUE);
/// <summary>
/// True if a scale move rate is set.
/// </summary>
public bool ScaleSet => (Scale != UNSET_VALUE);
/// <summary>
/// True if any move rate is set.
/// </summary>
public bool AnySet => (PositionSet || RotationSet || ScaleSet);
/// <summary>
/// True if position move rate should be instant.
/// </summary>
public bool InstantPosition => (Position == INSTANT_VALUE);
/// <summary>
/// True if rotation move rate should be instant.
/// </summary>
public bool InstantRotation => (Rotation == INSTANT_VALUE);
/// <summary>
/// True if scale move rate should be instant.
/// </summary>
public bool InstantScale => (Scale == INSTANT_VALUE);
/// <summary>
/// Sets all rates to instant.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetInstantRates()
{
Update(INSTANT_VALUE);
}
/// <summary>
/// Sets all rates to the same value.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(float value)
{
Update(value, value, value);
}
/// <summary>
/// Sets rates for each property.
/// </summary>
public void Update(float position, float rotation, float scale)
{
Position = position;
Rotation = rotation;
Scale = scale;
}
/// <summary>
/// Value used when data is not set.
/// </summary>
public const float UNSET_VALUE = float.NegativeInfinity;
/// <summary>
/// Value used when move rate should be instant.
/// </summary>
public const float INSTANT_VALUE = float.PositiveInfinity;
}
/// <summary>
/// Data to be used to configure smoothing for an owned predicted object.
/// </summary>
internal class MoveRatesCls : IResettable
{
public float Position;
public float Rotation;
public float Scale;
public float LastMultiplier { get; private set; } = 1f;
public MoveRatesCls(float value)
{
Position = value;
Rotation = value;
Scale = value;
}
public MoveRatesCls(float position, float rotation)
{
Position = position;
Rotation = rotation;
Scale = INSTANT_VALUE;
}
public MoveRatesCls(float position, float rotation, float scale)
{
Position = position;
Rotation = rotation;
Scale = scale;
}
/// <summary>
/// True if a positional move rate is set.
/// </summary>
public bool PositionSet => (Position != UNSET_VALUE);
/// <summary>
/// True if rotation move rate is set.
/// </summary>
public bool RotationSet => (Rotation != UNSET_VALUE);
/// <summary>
/// True if a scale move rate is set.
/// </summary>
public bool ScaleSet => (Scale != UNSET_VALUE);
/// <summary>
/// True if any move rate is set.
/// </summary>
public bool AnySet => (PositionSet || RotationSet || ScaleSet);
/// <summary>
/// True if position move rate should be instant.
/// </summary>
public bool InstantPosition => (Position == INSTANT_VALUE);
/// <summary>
/// True if rotation move rate should be instant.
/// </summary>
public bool InstantRotation => (Rotation == INSTANT_VALUE);
/// <summary>
/// True if scale move rate should be instant.
/// </summary>
public bool InstantScale => (Scale == INSTANT_VALUE);
public MoveRatesCls()
{
Update(UNSET_VALUE, UNSET_VALUE, UNSET_VALUE);
}
/// <summary>
/// Multiplies all rates by value.
/// </summary>
public void Multiply(float value)
{
LastMultiplier = value;
Position *= value;
Rotation *= value;
Scale *= value;
}
/// <summary>
/// Sets all rates to instant.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetInstantRates()
{
Update(INSTANT_VALUE);
}
/// <summary>
/// Sets all rates to the same value.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(float value)
{
Update(value, value, value);
}
/// <summary>
/// Updaes values.
/// </summary>
public void Update(float position, float rotation, float scale)
{
Position = position;
Rotation = rotation;
Scale = scale;
}
/// <summary>
/// Updaes values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(MoveRatesCls mr)
{
Update(mr.Position, mr.Rotation, mr.Scale);
}
public void ResetState()
{
Position = UNSET_VALUE;
Rotation = UNSET_VALUE;
Scale = UNSET_VALUE;
}
public void InitializeState() { }
/// <summary>
/// Value used when data is not set.
/// </summary>
public const float UNSET_VALUE = float.NegativeInfinity;
/// <summary>
/// Value used when move rate should be instant.
/// </summary>
public const float INSTANT_VALUE = float.PositiveInfinity;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bddf57861232884ca21f7e97a6d662d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,243 @@
using FishNet.Utility.Extension;
using GameKit.Utilities;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Object.Prediction
{
internal class SetInterpolationSmoother
{
#if PREDICTION_V2
#region Private.
/// <summary>
/// How quickly to move towards goal values.
/// </summary>
private MoveRates _moveRates;
/// <summary>
/// Local values of the graphical object when this was initialized.
/// </summary>
private TransformProperties _graphicalInitializedLocalValues;
/// <summary>
/// Values of the graphical object during PreTick or PreReplay.
/// </summary>
private TransformProperties _graphicalPreSimulateWorldValues;
/// <summary>
/// SmoothingData to use.
/// </summary>
private SetInterpolationSmootherData _smoothingData;
#endregion
/// <summary>
/// Initializes this smoother; should only be completed once.
/// </summary>
/// <param name="data"></param>
internal void InitializeOnce(SetInterpolationSmootherData data)
{
_smoothingData = data;
SetGraphicalObject(data.GraphicalObject);
_moveRates = new MoveRates(MoveRates.UNSET_VALUE);
_graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties();
}
/// <summary>
/// Sets GraphicalObject; can be changed at runtime.
/// </summary>
/// <param name="value"></param>
internal void SetGraphicalObject(Transform value)
{
_smoothingData.GraphicalObject = value;
_graphicalInitializedLocalValues.Update(value.localPosition, value.localRotation, value.localScale);
}
/// <summary>
/// Sets the interpolation value to use when the owner of this object.
/// </summary>
/// <param name="value">New interpolation value.</param>
internal void SetInterpolation(byte value)
{
if (value < 1)
value = 1;
_smoothingData.Interpolation = value;
}
/// <summary>
/// Called every frame.
/// </summary>
internal void Update()
{
if (CanSmooth())
MoveToTarget();
}
/// <summary>
/// Called when the TimeManager invokes OnPreTick.
/// </summary>
internal void OnPreTick()
{
if (CanSmooth())
{
/* Only snap to destination if interpolation is 1.
* This ensures the graphics will be at the proper location
* before the next movement rates are calculated. */
if (_smoothingData.Interpolation == 1)
ResetGraphicalToInitializedLocalOffsets(true, true);
_graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties();
}
}
/// <summary>
/// Called when TimeManager invokes OnPostTick.
/// </summary>
internal void OnPostTick()
{
if (CanSmooth())
{
_smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPreSimulateWorldValues.Position, _graphicalPreSimulateWorldValues.Rotation);
SetGraphicalMoveRates();
}
}
/// <summary>
/// Returns if prediction can be used on this rigidbody.
/// </summary>
/// <returns></returns>
private bool CanSmooth()
{
NetworkObject nob = _smoothingData.NetworkObject;
if (nob == null)
return false;
if (nob.IsServerOnly)
return false;
if (!nob.IsHost && nob.SpectatorAdaptiveInterpolation && !nob.IsOwner)
return false;
return true;
}
/// <summary>
/// Moves transform to target values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MoveToTarget()
{
//No rates are set.
if (!_moveRates.AnySet)
return;
Vector3 posGoal = GetGraphicalGoalPosition();
Quaternion rotGoal = GetGraphicalGoalRotation();
/* Only try to update properties if they have a valid move rate.
* Properties may have 0f move rate if they did not change. */
Transform t = _smoothingData.GraphicalObject;
float delta = Time.deltaTime;
//Position.
if (_smoothingData.SmoothPosition)
{
if (_moveRates.InstantPosition)
ResetGraphicalToInitializedLocalOffsets(true, false);
else if (_moveRates.PositionSet)
t.localPosition = Vector3.MoveTowards(t.localPosition, posGoal, _moveRates.Position * delta);
}
//Rotation.
if (_smoothingData.SmoothRotation)
{
if (_moveRates.InstantRotation)
ResetGraphicalToInitializedLocalOffsets(false, true);
else if (_moveRates.RotationSet)
t.localRotation = Quaternion.RotateTowards(t.localRotation, rotGoal, _moveRates.Rotation * delta);
}
if (GraphicalObjectMatchesLocalValues(posGoal, rotGoal))
_moveRates.Update(MoveRates.UNSET_VALUE);
}
/// <summary>
/// Returns if this transform matches arguments.
/// </summary>
/// <returns></returns>
private bool GraphicalObjectMatchesLocalValues(Vector3 position, Quaternion rotation)
{
bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.localPosition == position));
bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.localRotation == rotation));
return (positionMatches && rotationMatches);
}
/// <summary>
/// Sets Position and Rotation move rates to reach Target datas.
/// </summary>
private void SetGraphicalMoveRates()
{
uint interval = _smoothingData.Interpolation;
float delta = (float)_smoothingData.NetworkObject?.TimeManager.TickDelta;
float rate;
float distance;
Transform t = _smoothingData.GraphicalObject;
/* Position. */
rate = t.localPosition.GetRate(_graphicalInitializedLocalValues.Position, delta, out distance, interval);
//If qualifies for teleporting.
if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
{
_moveRates.Update(MoveRates.INSTANT_VALUE);
}
//Smoothing.
else
{
float positionRate = rate.SetIfUnderTolerance(0.0001f, MoveRates.INSTANT_VALUE);
rate = t.localRotation.GetRate(_graphicalInitializedLocalValues.Rotation, delta, out _, interval);
float rotationRate = rate.SetIfUnderTolerance(0.2f, MoveRates.INSTANT_VALUE);
_moveRates.Update(positionRate, rotationRate, MoveRates.UNSET_VALUE);
}
}
/// <summary>
/// Gets a goal position for the graphical object.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Vector3 GetGraphicalGoalPosition()
{
if (_smoothingData.SmoothPosition)
return _graphicalInitializedLocalValues.Position;
else
return _smoothingData.GraphicalObject.position;
}
/// <summary>
/// Gets a goal rotation for the graphical object.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Quaternion GetGraphicalGoalRotation()
{
if (_smoothingData.SmoothRotation)
return _graphicalInitializedLocalValues.Rotation;
else
return _smoothingData.GraphicalObject.rotation;
}
/// <summary>
/// Resets the graphical object to it's transform offsets during instantiation.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ResetGraphicalToInitializedLocalOffsets(bool position, bool rotation)
{
Transform graphical = _smoothingData.GraphicalObject;
if (position)
graphical.localPosition = GetGraphicalGoalPosition();
if (rotation)
graphical.localRotation = GetGraphicalGoalRotation();
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a2adf11043050fb4dbd1c3893c66c9f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
using UnityEngine;
namespace FishNet.Object.Prediction
{
/// <summary>
/// Data to be used to configure smoothing for an owned predicted object.
/// </summary>
internal struct SetInterpolationSmootherData
{
public Transform GraphicalObject;
public byte Interpolation;
public NetworkObject NetworkObject;
public bool SmoothPosition;
public bool SmoothRotation;
public bool SmoothScale;
public float TeleportThreshold;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5fecbbc171a81f4a8aaf30aebb1caef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: