Hi,
I have noticed that invalid values can sometimes be applied to the previous/backup value in a Prediction Smoothing Action. To create a simple reproduction, I added the following system to the Asteroids Sample:
using Unity.Burst;
using Unity.Entities;
using Unity.NetCode;
using Unity.Transforms;
using System;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
namespace Samples.Asteroids.Client.Systems
{
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[CreateAfter(typeof(GhostPredictionSmoothingSystem))]
public partial struct RegisterPredictionSmoothingSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
// ReSharper disable once Unity.Entities.SingletonMustBeRequested
var ghostPredictionSmoothing = SystemAPI.GetSingleton<GhostPredictionSmoothing>();
ghostPredictionSmoothing.RegisterSmoothingAction<LocalTransform>(state.EntityManager,
MyTranslationSmoothingAction.Action);
}
}
/// <summary>
/// The default prediction error <see cref="SmoothingAction"/> function for the <see cref="Translation"/> component.
/// Supports the user data that lets you customize the clamping and snapping of the translation component (any time the translation prediction error is too large).
/// </summary>
[BurstCompile]
public unsafe struct MyTranslationSmoothingAction
{
/// <summary>
/// The default value for the <see cref="DefaultSmoothingActionUserParams"/> if the no user data is passed to the function.
/// Position is corrected if the prediction error is at least 1 unit (usually mt) and less than 10 unit (usually mt)
/// </summary>
public sealed class DefaultStaticUserParams
{
internal static readonly SharedStatic<float> maxDist =
SharedStatic<float>.GetOrCreate<DefaultStaticUserParams, MaxDistKey>();
internal static readonly SharedStatic<float> delta =
SharedStatic<float>.GetOrCreate<DefaultStaticUserParams, DeltaKey>();
static DefaultStaticUserParams()
{
maxDist.Data = 10;
delta.Data = 1;
}
class MaxDistKey
{
}
class DeltaKey
{
}
}
/// <summary>
/// Return a the burst compatible function pointer that can be used to register the smoothing action to the
/// <see cref="GhostPredictionSmoothing"/> singleton.
/// </summary>
public static readonly PortableFunctionPointer<GhostPredictionSmoothing.SmoothingActionDelegate> Action =
new PortableFunctionPointer<GhostPredictionSmoothing.SmoothingActionDelegate>(SmoothingAction);
[BurstCompile(DisableDirectCall = true)]
[AOT.MonoPInvokeCallback(typeof(GhostPredictionSmoothing.SmoothingActionDelegate))]
private static void SmoothingAction(IntPtr currentData, IntPtr previousData, IntPtr usrData)
{
ref var trans = ref UnsafeUtility.AsRef<LocalTransform>((void*) currentData);
ref var backup = ref UnsafeUtility.AsRef<LocalTransform>((void*) previousData);
float maxDist = DefaultStaticUserParams.maxDist.Data;
float delta = DefaultStaticUserParams.delta.Data;
if (usrData.ToPointer() != null)
{
ref var userParam = ref UnsafeUtility.AsRef<DefaultSmoothingActionUserParams>(usrData.ToPointer());
maxDist = userParam.maxDist;
delta = userParam.delta;
}
var dist = math.distance(trans.Position, backup.Position);
UnityEngine.Debug.Log($"cp: {trans.Position} bp: {backup.Position}");
if (dist < maxDist && dist > delta && dist > 0)
{
trans.Position = backup.Position + (trans.Position - backup.Position) * delta / dist;
}
}
}
}
The MyTranslationSmoothingAction is a copy of the DefaultTranslationSmoothingAction with a debug added. The debug statement will sometimes generate the following result:
Erroneous NaN and near zero values are sometimes loaded into the backup / previous LocalTransform.