#if FISHNET_THREADED_COLLIDER_ROLLBACK
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using FishNet.Managing;
using Unity.Collections;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.Jobs;
namespace FishNet.Component.ColliderRollback
{
///
/// Holds all job-friendly data for collider rollback: flattened transform table,
/// ring buffers of TRS snapshots, and a persistent rolled-back mask.
///
public sealed partial class RollbackCollection
{
#region Private.
///
/// NetworkManager.
///
private NetworkManager _networkManager;
///
/// True if the collection is configured and has valid buffers.
///
private bool _ready;
///
/// Ring buffer length per rolling entry.
///
private int _maxSnapshots;
///
/// Physics used when rolling back.
///
private RollbackPhysicsType _rollbackPhysics;
///
/// List of ColliderRollback.
///
private readonly List _colliderRollbacks = new();
///
/// Map of ColliderRollback -> index.
///
private readonly Dictionary _colliderRollbacksIndices = new();
///
/// Write array for Rollback requests for deferred Rollback.
///
private NativeList _writeRequests;
///
/// Read array for Rollback requests for deferred Rollback.
///
private NativeList _readRequests;
///
/// Physics that requested for deferred rollback.
///
private RollbackPhysicsType _requestsRollbackPhysics;
///
/// TransformAccessArray for ColliderRollbacks.
///
private TransformAccessArray _colliderRollbacksTAA;
///
/// TransformAccessArray for RollingColliders.
///
private TransformAccessArray _rollingCollidersTAA;
///
/// Snapshots of ColliderRollbacks: [colliderRollbackIndex].
///
private NativeList _colliderRollbacksSnapshots;
///
/// Flattened snapshots ring-buffer of RollingColliders: [rollingColliderIndex * MaxSnapshots + frame].
///
private NativeList _rollingCollidersSnapshots;
///
/// ColliderRollbacks-level rolled-back flags (0 = write, 1 = freeze).
///
private NativeList _colliderRollbacksRolledBackMask;
///
/// Per-colliderRollbacks scene handle to allow filtering in jobs without masks.
///
private NativeList _colliderRollbacksSceneHandles;
///
/// Per-colliderRollbacks number of available history frames for lerp (clamped to MaxSnapshots).
///
private NativeList _colliderRollbacksLerpFrames;
///
/// Per-colliderRollbacks BoundingBoxData.
///
private NativeList _colliderRollbacksBoundingBoxData;
///
/// Maps rolling-collider index to its colliderRollbacks index.
///
private NativeList _rollingColliderToColliderRollbacks;
/// x
/// Per-rolling write pointer (next frame slot in the ring).
///
private NativeList _rollingCollidersWriteIndices;
#endregion
~RollbackCollection() { Deinitialize(); }
///
/// Initialize ring size based on network timing. Call once on startup or when timing changes.
///
internal void Initialize(NetworkManager networkManager, double tickDelta, float maximumRollbackTime)
{
_networkManager = networkManager;
_maxSnapshots = Mathf.Max(2, Mathf.CeilToInt((float)(maximumRollbackTime / tickDelta)));
if (!_writeRequests.IsCreated) _writeRequests = new NativeList(64, Allocator.Persistent);
if (!_readRequests.IsCreated) _readRequests = new NativeList(64, Allocator.Persistent);
if (!_colliderRollbacksTAA.isCreated) _colliderRollbacksTAA = new TransformAccessArray(64);
if (!_rollingCollidersTAA.isCreated) _rollingCollidersTAA = new TransformAccessArray(64);
if (!_colliderRollbacksSnapshots.IsCreated) _colliderRollbacksSnapshots = new NativeList(64, Allocator.Persistent);
if (!_rollingCollidersSnapshots.IsCreated) _rollingCollidersSnapshots = new NativeList(64, Allocator.Persistent);
if (!_colliderRollbacksRolledBackMask.IsCreated) _colliderRollbacksRolledBackMask = new NativeList(64, Allocator.Persistent);
if (!_colliderRollbacksSceneHandles.IsCreated) _colliderRollbacksSceneHandles = new NativeList(64, Allocator.Persistent);
if (!_colliderRollbacksLerpFrames.IsCreated) _colliderRollbacksLerpFrames = new NativeList(64, Allocator.Persistent);
if (!_colliderRollbacksBoundingBoxData.IsCreated) _colliderRollbacksBoundingBoxData = new NativeList(64, Allocator.Persistent);
if (!_rollingColliderToColliderRollbacks.IsCreated) _rollingColliderToColliderRollbacks = new NativeList(64, Allocator.Persistent);
if (!_rollingCollidersWriteIndices.IsCreated) _rollingCollidersWriteIndices = new NativeList(64, Allocator.Persistent);
_ready = true;
}
///
/// Deinitialize all native buffers and transform access arrays.
///
internal void Deinitialize()
{
_networkManager = null;
if (_writeRequests.IsCreated) _writeRequests.Dispose();
if (_readRequests.IsCreated) _readRequests.Dispose();
if (_colliderRollbacksTAA.isCreated) _colliderRollbacksTAA.Dispose();
if (_rollingCollidersTAA.isCreated) _rollingCollidersTAA.Dispose();
if (_colliderRollbacksSnapshots.IsCreated) _colliderRollbacksSnapshots.Dispose();
if (_rollingCollidersSnapshots.IsCreated) _rollingCollidersSnapshots.Dispose();
if (_colliderRollbacksRolledBackMask.IsCreated) _colliderRollbacksRolledBackMask.Dispose();
if (_colliderRollbacksSceneHandles.IsCreated) _colliderRollbacksSceneHandles.Dispose();
if (_colliderRollbacksLerpFrames.IsCreated) _colliderRollbacksLerpFrames.Dispose();
if (_colliderRollbacksBoundingBoxData.IsCreated) _colliderRollbacksBoundingBoxData.Dispose();
if (_rollingColliderToColliderRollbacks.IsCreated) _rollingColliderToColliderRollbacks.Dispose();
if (_rollingCollidersWriteIndices.IsCreated) _rollingCollidersWriteIndices.Dispose();
_colliderRollbacks.Clear();
_colliderRollbacksIndices.Clear();
_ready = false;
}
///
/// Ensure capacities for upcoming additions without reallocations.
///
private void EnsureCapacity(int addRollingColliders, int addColliderRollbacks)
{
if (!_ready)
{
_networkManager.LogError("RollbackCollection is not configured. Call Configure(NetworkManager, double, float) first.");
return;
}
int newColliderRollbacksCount = _colliderRollbacksTAA.length + Math.Max(0, addColliderRollbacks);
int newRollingCollidersCount = _rollingCollidersTAA.length + Math.Max(0, addRollingColliders);
if (_colliderRollbacksTAA.capacity < newColliderRollbacksCount)
_colliderRollbacksTAA.capacity = newColliderRollbacksCount;
if (_rollingCollidersTAA.capacity < newRollingCollidersCount)
_rollingCollidersTAA.capacity = newRollingCollidersCount;
if (_colliderRollbacksSnapshots.Capacity < newColliderRollbacksCount)
_colliderRollbacksSnapshots.Capacity = newColliderRollbacksCount;
if (_rollingCollidersSnapshots.Capacity < newRollingCollidersCount * _maxSnapshots)
_rollingCollidersSnapshots.Capacity = newRollingCollidersCount * _maxSnapshots;
if (_colliderRollbacksRolledBackMask.Capacity < newColliderRollbacksCount)
_colliderRollbacksRolledBackMask.Capacity = newColliderRollbacksCount;
if (_colliderRollbacksSceneHandles.Capacity < newColliderRollbacksCount)
_colliderRollbacksSceneHandles.Capacity = newColliderRollbacksCount;
if (_colliderRollbacksLerpFrames.Capacity < newColliderRollbacksCount)
_colliderRollbacksLerpFrames.Capacity = newColliderRollbacksCount;
if (_colliderRollbacksBoundingBoxData.Capacity < newColliderRollbacksCount)
_colliderRollbacksBoundingBoxData.Capacity = newColliderRollbacksCount;
if (_rollingColliderToColliderRollbacks.Capacity < newRollingCollidersCount)
_rollingColliderToColliderRollbacks.Capacity = newRollingCollidersCount;
if (_rollingCollidersWriteIndices.Capacity < newRollingCollidersCount)
_rollingCollidersWriteIndices.Capacity = newRollingCollidersCount;
}
///
/// Registers a ColliderRollback with all its RollingColliders.
/// Adds new rolling entries at the end (dense indexing).
///
internal void RegisterColliderRollback(ColliderRollback colliderRollback)
{
if (!_ready)
{
_networkManager.LogError("RollbackCollection is not configured. Call Configure(NetworkManager, double, float) first.");
return;
}
if (_colliderRollbacksIndices.ContainsKey(colliderRollback)) return;
IReadOnlyList list = colliderRollback.GetRollingColliders();
int addColliders = list.Count;
int newColliderRollbacksCount = _colliderRollbacks.Count + 1;
int newRollingCollidersCount = _rollingCollidersTAA.length + addColliders;
EnsureCapacity(addColliders, 1);
_colliderRollbacks.Add(colliderRollback);
_colliderRollbacksIndices[colliderRollback] = newColliderRollbacksCount - 1;
_colliderRollbacksTAA.Add(colliderRollback.transform);
_colliderRollbacksSnapshots.ResizeUninitialized(newColliderRollbacksCount);
_colliderRollbacksRolledBackMask.Add(0);
_colliderRollbacksSceneHandles.Add(colliderRollback.gameObject.scene.handle);
_colliderRollbacksLerpFrames.Add(0);
_colliderRollbacksBoundingBoxData.Add(colliderRollback.GetBoundingBoxData());
_rollingCollidersSnapshots.ResizeUninitialized(newRollingCollidersCount * _maxSnapshots);
for (int i = 0; i < addColliders; i++)
{
Transform rollingCollider = list[i];
_rollingCollidersWriteIndices.Add(0);
_rollingColliderToColliderRollbacks.Add(newColliderRollbacksCount - 1);
_rollingCollidersTAA.Add(rollingCollider);
}
}
///
/// Unregisters a ColliderRollback. Removes all its rolling entries.
/// Uses swap-back removal for both rolling entries and the colliderRollbacks, keeping indices dense.
///
internal void UnregisterColliderRollback(ColliderRollback cr)
{
if (!_ready) return;
if (!_colliderRollbacksIndices.Remove(cr, out int colliderRollbacksIndex)) return;
int lastColliderRollbacks = _colliderRollbacks.Count - 1;
// Remove all rolling entries belonging to this colliderRollbacks (scan backwards for stability).
for (int i = _rollingColliderToColliderRollbacks.Length - 1; i >= 0; --i)
{
if (_rollingColliderToColliderRollbacks[i] == colliderRollbacksIndex)
RemoveRollingColliderAtSwapBack(i);
}
// Remove the colliderRollbacks by swapping with the last colliderRollbacks.
if (colliderRollbacksIndex != lastColliderRollbacks)
{
ColliderRollback tempCr = _colliderRollbacks[lastColliderRollbacks];
_colliderRollbacksIndices[tempCr] = colliderRollbacksIndex;
_colliderRollbacks[colliderRollbacksIndex] = tempCr;
// Re-tag colliders that belonged to lastColliderRollbacks to now point to colliderRollbacksIndex.
for (int i = 0; i < _rollingColliderToColliderRollbacks.Length; i++)
{
if (_rollingColliderToColliderRollbacks[i] == lastColliderRollbacks)
_rollingColliderToColliderRollbacks[i] = colliderRollbacksIndex;
}
}
_colliderRollbacksTAA.RemoveAtSwapBack(colliderRollbacksIndex);
_colliderRollbacksRolledBackMask.RemoveAtSwapBack(colliderRollbacksIndex);
_colliderRollbacksSceneHandles.RemoveAtSwapBack(colliderRollbacksIndex);
_colliderRollbacksLerpFrames.RemoveAtSwapBack(colliderRollbacksIndex);
_colliderRollbacksBoundingBoxData.RemoveAtSwapBack(colliderRollbacksIndex);
_colliderRollbacks.RemoveAt(lastColliderRollbacks);
}
///
/// Removes one rolling entry at index by swapping with the last item.
/// Updates all parallel structures and GlobalIndex on the moved entry.
///
internal void RemoveRollingColliderAtSwapBack(int rollingColliderIndex)
{
int last = _rollingCollidersTAA.length - 1;
if (last < 0) return;
_rollingCollidersTAA.RemoveAtSwapBack(rollingColliderIndex);
_rollingCollidersWriteIndices.RemoveAtSwapBack(rollingColliderIndex);
_rollingColliderToColliderRollbacks.RemoveAtSwapBack(rollingColliderIndex);
if (rollingColliderIndex != last)
{
// Move last snapshots ring over the removed slot.
int dst = rollingColliderIndex * _maxSnapshots;
int src = last * _maxSnapshots;
for (int k = 0; k < _maxSnapshots; k++)
_rollingCollidersSnapshots[dst + k] =_rollingCollidersSnapshots[src + k];
}
_rollingCollidersSnapshots.ResizeUninitialized(_rollingCollidersSnapshots.Length - _maxSnapshots);
}
///
/// Populates one snapshot for every non-rolled-back transform using a parallel job.
/// Call this once per tick on the server (e.g., from OnPostTick).
///
internal void CreateSnapshots()
{
if (!_ready) return;
if (_colliderRollbacksTAA.length > 0 && _rollingCollidersTAA.length > 0)
{
JobHandle first = new RollbackManager.IncrementGroupsFramesJob
{
maxSnapshots = _maxSnapshots,
colliderRollbacksLerpFrames = _colliderRollbacksLerpFrames.AsArray(),
colliderRollbacksRolledBackMask = _colliderRollbacksRolledBackMask.AsArray(),
}.Schedule(_colliderRollbacksTAA.length, 64);
JobHandle second = new RollbackManager.PopulateColliderRollbackSnapshotsJob
{
colliderRollbackSnapshots = _colliderRollbacksSnapshots.AsArray(),
colliderRollbacksRolledBackMask = _colliderRollbacksRolledBackMask.AsArray()
}.Schedule(_colliderRollbacksTAA, first);
JobHandle third = new RollbackManager.PopulateRollingColliderSnapshotsJob
{
maxSnapshots = _maxSnapshots,
rollingCollidersSnapshots = _rollingCollidersSnapshots.AsArray(),
rollingCollidersWriteIndices = _rollingCollidersWriteIndices.AsArray(),
colliderRollbacksRolledBackMask = _colliderRollbacksRolledBackMask.AsArray(),
colliderToColliderRollbacks = _rollingColliderToColliderRollbacks.AsArray()
}.Schedule(_rollingCollidersTAA, second);
third.Complete();
}
}
#region Sinlge ColliderRollback
///
/// Computes lerp mode/endFrame/percent based on 'time' and applies rollback to the whole colliderRollbacks.
///
internal void Rollback(ColliderRollback colliderRollback, float time, RollbackPhysicsType rollbackPhysicsType)
{
if (!_ready) return;
if (!_colliderRollbacksIndices.TryGetValue(colliderRollback, out int colliderRollbacksIndex)) return;
// Already rolled back.
if (IsRolledBack(colliderRollback))
{
_networkManager.LogWarning("Colliders are already rolled back. Returning colliders forward first.");
Return(colliderRollback, rollbackPhysicsType);
}
int frames = _colliderRollbacksLerpFrames[colliderRollbacksIndex];
if (frames == 0) return;
/* If time were 0.3f and delta was 0.2f then the
* result would be 1.5f. This indicates to lerp between
* the first snapshot, and one after. */
float decimalFrame = time / (float)_networkManager.TimeManager.TickDelta;
RollbackManager.FrameRollbackTypes mode;
int endFrame;
float percent;
/* Rollback is beyond written quantity.
* Set to use the last snapshot. */
if (decimalFrame > frames)
{
mode = RollbackManager.FrameRollbackTypes.Exact;
// Be sure to subtract 1 to get last entry in snapshots.
endFrame = frames - 1;
// Not needed for exact but must be set.
percent = 1f;
}
else
{
percent = decimalFrame % 1f;
endFrame = Mathf.CeilToInt(decimalFrame);
/* If the end frame is larger than or equal to 1
* then a lerp between two snapshots can occur. If
* equal to 1 then the lerp would occur between 0 and 1. */
if (endFrame >= 1)
{
mode = RollbackManager.FrameRollbackTypes.LerpMiddle;
}
// Rolling back only 1 frame.
else
{
endFrame = 0;
mode = RollbackManager.FrameRollbackTypes.LerpFirst;
}
}
// Apply to all rolling entries belonging to this colliderRollbacks.
for (int i = 0; i < _rollingColliderToColliderRollbacks.Length; i++)
if (_rollingColliderToColliderRollbacks[i] == colliderRollbacksIndex)
ApplyRollbackIndex(i, endFrame, percent, mode);
_colliderRollbacksRolledBackMask[colliderRollbacksIndex] = 1;
_rollbackPhysics |= rollbackPhysicsType;
SyncTransforms(rollbackPhysicsType);
}
///
/// Called when a specific colliderRollbacks should return.
///
internal void Return(ColliderRollback colliderRollback, RollbackPhysicsType rollbackPhysicsType)
{
if (!_ready) return;
if (!_colliderRollbacksIndices.TryGetValue(colliderRollback, out int colliderRollbacksIndex)) return;
if (!IsRolledBack(colliderRollback))
return;
// Iterate dense rolling entries and return only those that belong to this colliderRollbacks.
for (int i = 0; i < _rollingColliderToColliderRollbacks.Length; i++)
{
if (_rollingColliderToColliderRollbacks[i] == colliderRollbacksIndex)
{
int frames = _colliderRollbacksLerpFrames[colliderRollbacksIndex];
if (frames <= 0)
continue;
int writeIdx = _rollingCollidersWriteIndices[i];
int baseOffset = i * _maxSnapshots;
int lastIdx = (writeIdx - 1 + _maxSnapshots) % _maxSnapshots;
// Return to the newest (last written) snapshot
int snapshotIndex = baseOffset + lastIdx;
ColliderSnapshot s = _rollingCollidersSnapshots[snapshotIndex];
Transform t = _rollingCollidersTAA[i];
t.SetPositionAndRotation(s.WorldPosition, s.WorldRotation);
}
}
_colliderRollbacksRolledBackMask[colliderRollbacksIndex] = 0;
_rollbackPhysics |= rollbackPhysicsType;
SyncTransforms(rollbackPhysicsType);
}
///
/// Applies a rollback for a specific global transform index using the provided interpolation mode.
///
/// RollingCollider index into the ColliderRollback.
/// Frame interpolation mode.
/// Target history frame index (0 = newest).
/// Lerp factor for interpolation modes.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ApplyRollbackIndex(int rollingColliderIdx, int endFrame, float percent,
RollbackManager.FrameRollbackTypes mode)
{
if (!_ready || rollingColliderIdx < 0 || rollingColliderIdx >= _rollingCollidersTAA.length)
return;
int colliderRollbacksIndex = _rollingColliderToColliderRollbacks[rollingColliderIdx];
int writeIdx = _rollingCollidersWriteIndices[rollingColliderIdx];
int baseOffset = rollingColliderIdx * _maxSnapshots;
int lastIdx = (writeIdx - 1 + _maxSnapshots) % _maxSnapshots;
bool isRecycled = _colliderRollbacksLerpFrames[colliderRollbacksIndex] >= _maxSnapshots;
Transform t = _rollingCollidersTAA[rollingColliderIdx];
if (mode == RollbackManager.FrameRollbackTypes.Exact)
{
ColliderSnapshot s = _rollingCollidersSnapshots[BufIndex(baseOffset, endFrame, lastIdx, isRecycled, _maxSnapshots)];
t.SetPositionAndRotation(s.WorldPosition, s.WorldRotation);
}
else if (mode == RollbackManager.FrameRollbackTypes.LerpFirst)
{
ColliderSnapshot s = _rollingCollidersSnapshots[BufIndex(baseOffset, endFrame, lastIdx, isRecycled, _maxSnapshots)];
t.GetPositionAndRotation(out Vector3 curPos, out Quaternion curRot);
t.SetPositionAndRotation(Vector3.Lerp(curPos, s.WorldPosition, percent),
Quaternion.Lerp(curRot, s.WorldRotation, percent));
}
else if (mode == RollbackManager.FrameRollbackTypes.LerpMiddle)
{
ColliderSnapshot s0 = _rollingCollidersSnapshots[BufIndex(baseOffset, endFrame - 1, lastIdx, isRecycled, _maxSnapshots)];
ColliderSnapshot s1 = _rollingCollidersSnapshots[BufIndex(baseOffset, endFrame, lastIdx, isRecycled, _maxSnapshots)];
t.SetPositionAndRotation(Vector3.Lerp(s0.WorldPosition, s1.WorldPosition, percent),
Quaternion.Lerp(s0.WorldRotation, s1.WorldRotation, percent));
}
return;
// compute buffer index with negative-safe modulo
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int BufIndex(int baseOffset, int history, int lastIdx, bool isRecycled, int maxSnapshots)
{
int idx = baseOffset + lastIdx - history;
// If negative value start taking from the back.
if (idx < 0)
{
/* Cannot take from back, snapshots aren't filled yet.
* Instead take the oldest snapshot, which in this case
* would be index baseOffset. */
if (!isRecycled)
return baseOffset;
// Snapshots filled, take from back.
else
return idx + maxSnapshots;
}
// Not a negative value, return as is.
else return idx;
}
}
///
/// Sets rolled-back state for a colliderRollbacks in O(1). No per-rolling writes.
///
/// True if the colliderRollbacks is currently rolled back.
public bool IsRolledBack(ColliderRollback cr)
=> _ready && _colliderRollbacksIndices.TryGetValue(cr, out int g) && _colliderRollbacksRolledBackMask[g] != 0;
#endregion
#region Job ColliderRollback
///
/// Run rollback for ALL colliderRollbacks in parallel (jobified).
///
internal void Rollback(int sceneHandle, float time, RollbackPhysicsType rollbackPhysicsType)
{
if (!_ready || _rollingCollidersTAA.length == 0)
return;
Return();
JobHandle job = new RollbackManager.ApplyRollbackJob
{
sceneHandle = sceneHandle,
maxSnapshots = _maxSnapshots,
decimalFrame = time / (float)_networkManager.TimeManager.TickDelta,
colliderToColliderRollbacks = _rollingColliderToColliderRollbacks.AsArray(),
colliderRollbacksSceneHandles = _colliderRollbacksSceneHandles.AsArray(),
colliderRollbacksLerpFrames = _colliderRollbacksLerpFrames.AsArray(),
rollingCollidersWriteIndices = _rollingCollidersWriteIndices.AsArray(),
colliderRollbacksRolledBackMask = _colliderRollbacksRolledBackMask.AsArray(),
rollingCollidersSnapshots = _rollingCollidersSnapshots.AsArray()
}.Schedule(_rollingCollidersTAA);
job.Complete();
_rollbackPhysics |= rollbackPhysicsType;
SyncTransforms(rollbackPhysicsType);
}
///
/// Run rollback for intersected colliderRollbacks by ray in parallel (jobified).
///
internal void Rollback(RollbackManager.RollbackRequest rollbackRequest)
{
if (!_ready || _rollingCollidersTAA.length == 0)
return;
Return();
JobHandle job = new RollbackManager.ApplyRollbackRaycastJob
{
sceneHandle = rollbackRequest.sceneHandle,
maxSnapshots = _maxSnapshots,
decimalFrame = rollbackRequest.time / (float)_networkManager.TimeManager.TickDelta,
origin = rollbackRequest.origin,
dir = rollbackRequest.direction,
distance = rollbackRequest.distance,
physicsType = (int)rollbackRequest.rollbackPhysicsType,
colliderToColliderRollbacks = _rollingColliderToColliderRollbacks.AsArray(),
colliderRollbacksBoundingBoxData = _colliderRollbacksBoundingBoxData.AsArray(),
colliderRollbacksSceneHandles = _colliderRollbacksSceneHandles.AsArray(),
colliderRollbacksLerpFrames = _colliderRollbacksLerpFrames.AsArray(),
rollingCollidersWriteIndices = _rollingCollidersWriteIndices.AsArray(),
colliderRollbacksRolledBackMask = _colliderRollbacksRolledBackMask.AsArray(),
colliderRollbacksSnapshots = _colliderRollbacksSnapshots.AsArray(),
rollingCollidersSnapshots = _rollingCollidersSnapshots.AsArray()
}.Schedule(_rollingCollidersTAA);
job.Complete();
_rollbackPhysics |= rollbackRequest.rollbackPhysicsType;
SyncTransforms(rollbackRequest.rollbackPhysicsType);
}
///
/// Request rollback for deferred rollback for intersected colliderRollbacks by ray in parallel (jobified).
///
internal void RequestRollbackDeferred(RollbackManager.RollbackRequest rollbackRequest)
{
_writeRequests.Add(rollbackRequest);
_requestsRollbackPhysics |= rollbackRequest.rollbackPhysicsType;
}
///
/// Run rollback for all requests intersected colliderRollbacks by ray in parallel (jobified).
///
/// Count of requests.
internal int RollbackDeferred()
{
if (!_ready || _rollingCollidersTAA.length == 0 || _writeRequests.Length == 0)
return 0;
Return();
(_readRequests, _writeRequests) = (_writeRequests, _readRequests);
_writeRequests.Clear();
int groupCount = _colliderRollbacksTAA.length;
int batchSize = ComputeBatchSize(groupCount);
NativeArray sum = new NativeArray(groupCount, Allocator.TempJob);
NativeArray cnt = new NativeArray(groupCount, Allocator.TempJob);
try
{
JobHandle computeHandle = new RollbackManager.ComputeDeferredRollbackSumsJob
{
tickDelta = (float)_networkManager.TimeManager.TickDelta,
requests = _readRequests.AsArray(),
colliderRollbacksSceneHandles = _colliderRollbacksSceneHandles.AsArray(),
colliderRollbacksLerpFrames = _colliderRollbacksLerpFrames.AsArray(),
colliderRollbacksBoundingBoxData = _colliderRollbacksBoundingBoxData.AsArray(),
colliderRollbacksSnapshots = _colliderRollbacksSnapshots.AsArray(),
sumDecimalFrame = sum,
hitCount = cnt
}.Schedule(groupCount, batchSize);
JobHandle applyHandle = new RollbackManager.ApplyDeferredRollbackJob
{
maxSnapshots = _maxSnapshots,
colliderToColliderRollbacks = _rollingColliderToColliderRollbacks.AsArray(),
colliderRollbacksLerpFrames = _colliderRollbacksLerpFrames.AsArray(),
rollingCollidersWriteIndices = _rollingCollidersWriteIndices.AsArray(),
colliderRollbacksRolledBackMask = _colliderRollbacksRolledBackMask.AsArray(),
rollingCollidersSnapshots = _rollingCollidersSnapshots.AsArray(),
sumDecimalFrame = sum,
hitCount = cnt
}.Schedule(_rollingCollidersTAA, computeHandle);
applyHandle.Complete();
}
finally
{
sum.Dispose();
cnt.Dispose();
}
_rollbackPhysics |= _requestsRollbackPhysics;
_requestsRollbackPhysics = 0;
SyncTransforms(_rollbackPhysics);
return _readRequests.Length;
}
///
/// Run Return for ALL colliderRollbacks in parallel (jobified).
///
internal void Return()
{
if (!_ready || _rollingCollidersTAA.length == 0)
return;
JobHandle job = new RollbackManager.ReturnRollbackAllJob
{
maxSnapshots = _maxSnapshots,
colliderToColliderRollbacks = _rollingColliderToColliderRollbacks.AsArray(),
colliderRollbacksLerpFrames = _colliderRollbacksLerpFrames.AsArray(),
rollingCollidersWriteIndices = _rollingCollidersWriteIndices.AsArray(),
colliderRollbacksRolledBackMask = _colliderRollbacksRolledBackMask.AsArray(),
rollingCollidersSnapshots = _rollingCollidersSnapshots.AsArray()
}.Schedule(_rollingCollidersTAA);
job.Complete();
SyncTransforms(_rollbackPhysics);
}
#endregion
///
/// Applies transforms for the specified physics type.
///
///
private static void SyncTransforms(RollbackPhysicsType physicsType)
{
if ((physicsType & RollbackPhysicsType.Physics) > 0)
Physics.SyncTransforms();
if ((physicsType & RollbackPhysicsType.Physics2D) > 0)
Physics2D.SyncTransforms();
}
private static int ComputeBatchSize(int length, int minBatch = 1, int maxBatch = 128)
{
if (length <= 0) return 1;
// +1: main thread + worker threads
int workers = JobsUtility.JobWorkerCount + 1;
// Aim for ~4 waves of batches across all workers.
int targetBatches = Mathf.Max(1, workers * 4);
// CeilDiv to get iterations per batch
int batch = (length + targetBatches - 1) / targetBatches;
return Mathf.Clamp(batch, minBatch, maxBatch);
}
}
}
#endif