FirstGearGames / FishNet

FishNet: Unity Networking Evolved.
Other
1.28k stars 136 forks source link

System.ArgumentOutOfRangeException #298

Closed MrPorky closed 1 year ago

MrPorky commented 1 year ago

Unity version: 2022.2.5f1 Fish-Networking version: 3.4.3.pro

Describe the bug I'm creating a so far pretty simple CSP character controller. But when the client joins the server and the player spawns the NetworkBehaviour.Prediction gets the offset -1 with trhows the error System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index image

To Reproduce Steps to reproduce the behavior:

  1. Go to NetworkManager and add your player prefab on PlayerSpawner image
  2. Start server
  3. Join as client
  4. See error

Player Script `public class Player : NetworkBehaviour {

region Types.

public struct MoveData : IReplicateData
{
    public Vector2 Dir;

    private uint _tick;
    public void Dispose() { }
    public uint GetTick() => _tick;
    public void SetTick(uint value) => _tick = value;
}

public struct ReconcileData : IReconcileData
{
    public Vector3 Position;
    public Vector2 CurrentDir;
    public ReconcileData(Vector3 position, Vector2 currentDir)
    {
        Position = position;
        CurrentDir = currentDir;
        _tick = 0;
    }

    private uint _tick;
    public void Dispose() { }
    public uint GetTick() => _tick;
    public void SetTick(uint value) => _tick = value;
}
#endregion

#region Serialized.
[SerializeField]
private float _dampingSpeed = 2;
#endregion

#region Private.
private readonly int X_Hash = Animator.StringToHash("X");
private readonly int Y_Hash = Animator.StringToHash("Y");
private readonly int Still_Hash = Animator.StringToHash("Still");
private readonly int Attack_Hash = Animator.StringToHash("Attack");

private CharacterController _characterController;
private NetworkAnimator _networkAnimator;
private Animator _animator;

private Vector2 _currentDir = Vector2.zero;
#endregion

#region Predicted spawning.
/// <summary>
/// Prefab to spawn for predicted spawning.
/// </summary>
public NetworkObject BulletPrefab;

/// <summary>
/// True if a spawn is queued from input.
/// </summary>
private bool _spawnBullet;

/// <summary>
/// Last spawned bullet
/// </summary>
private NetworkObject _lastBullet;

#endregion

private void Awake()
{
    InstanceFinder.TimeManager.OnTick += TimeManager_OnTick;
    _characterController = GetComponent<CharacterController>();
    _networkAnimator = GetComponent<NetworkAnimator>();
    _animator = GetComponent<Animator>();
}

public override void OnStartClient()
{
    base.OnStartClient();

    if (base.IsOwner)
        GameInput.SetGameInputMode(GameInput.GameInputMode.Player);
}

public override void OnStartNetwork()
{
    base.OnStartNetwork();
    if (base.IsServer || base.IsClient)
        base.TimeManager.OnTick += TimeManager_OnTick;
}

public override void OnStopNetwork()
{
    base.OnStopNetwork();
    if (base.TimeManager != null)
        base.TimeManager.OnTick -= TimeManager_OnTick;
}

private void Update()
{
    if (!base.IsOwner) return;

    if (GameInput.GetFire() && _lastBullet == null)
    {
        _spawnBullet = true;
    }

}

private void TimeManager_OnTick()
{
    if (base.IsOwner)
    {
        Reconciliation(default, false);
        CheckInput(out MoveData md);
        Move(md, false);

        SetAnimatorValues();
        // TrySpawnBullet();
    }

    if (base.IsServer)
    {
        Move(default, true);
        ReconcileData rd = new ReconcileData(transform.position, _currentDir);
        Reconciliation(rd, true);
    }
}

private void SetAnimatorValues()
{
    _animator.SetFloat(X_Hash, _currentDir.x);
    _animator.SetFloat(Y_Hash, _currentDir.y);
    _animator.SetBool(Still_Hash, _currentDir.x == 0 && _currentDir.y == 0);
}

private void TrySpawnBullet()
{
    if (_spawnBullet)
    {
        _spawnBullet = false;

        NetworkObject nob = Instantiate(BulletPrefab, transform.position + (transform.forward * 1f), transform.rotation);
        _lastBullet = nob;

        //Spawn client side, which will send the predicted spawn to server.
        base.Spawn(nob, base.Owner);
    }
}

private void CheckInput(out MoveData md)
{
    md = default;
    md.Dir = GameInput.GetMovmentVector();
}

[Replicate]
private void Move(MoveData md, bool asServer, Channel channel = Channel.Unreliable, bool replaying = false)
{
    float delta = (float)base.TimeManager.TickDelta;

    Vector2 dirDif = md.Dir - _currentDir;
    Vector2 dir = md.Dir;

    if (dirDif.magnitude > .1)
    {
        dir = _currentDir + _dampingSpeed * delta * dirDif.normalized;
    }

    _currentDir = dir;
    _characterController.Move(new Vector3(dir.x, 0, dir.y) * delta);
}

[Reconcile]
private void Reconciliation(ReconcileData rd, bool asServer, Channel channel = Channel.Unreliable)
{
    transform.position = rd.Position;
    _currentDir = rd.CurrentDir;
}

}`

Full Error System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index at System.Collections.Generic.List1[T].get_Item (System.Int32 index) [0x00009] in <697c5b3fd45741b99b7cab46a4b7b6ac>:0 at FishNet.Serializing.Writer.WriteReplicate[T] (System.Collections.Generic.List1[T] values, System.Int32 offset) [0x0006e] in C:\Projects\SpellCurve\Assets\FishNet\Runtime\Serializing\Writer.cs:1136 at FishNet.Object.NetworkBehaviour.SendReplicateRpc[T] (System.UInt32 hash, System.Collections.Generic.List1[T] replicates, FishNet.Transporting.Channel channel) [0x000b2] in C:\Projects\SpellCurve\Assets\FishNet\Runtime\Object\NetworkBehaviour.Prediction.cs:260 at FishNet.Object.NetworkBehaviour.Replicate_Client_Internal[T] (FishNet.Object.Prediction.Delegating.ReplicateUserLogicDelegate1[T] del, System.UInt32 methodHash, System.Collections.Generic.List1[T] replicates, T data, FishNet.Transporting.Channel channel) [0x00167] in C:\Projects\SpellCurve\Assets\FishNet\Runtime\Object\NetworkBehaviour.Prediction.cs:536 at Player.Move (Player+MoveData md, System.Boolean asServer, FishNet.Transporting.Channel channel, System.Boolean replaying) [0x00032] in <6cf1e58e52df4a24b637609659c130f5>:0 at Player.TimeManager_OnTick () [0x00027] in C:\Projects\SpellCurve\Assets\Script\Player.cs:127 at (wrapper delegate-invoke) .invoke_void() at FishNet.Managing.Timing.TimeManager.IncreaseTick () [0x0011a] in C:\Projects\SpellCurve\Assets\FishNet\Runtime\Managing\Timing\TimeManager.cs:658 at FishNet.Managing.Timing.TimeManager.g__MethodLogic|96_0 () [0x00001] in C:\Projects\SpellCurve\Assets\FishNet\Runtime\Managing\Timing\TimeManager.cs:341 at FishNet.Managing.Timing.TimeManager.TickUpdate () [0x00067] in C:\Projects\SpellCurve\Assets\FishNet\Runtime\Managing\Timing\TimeManager.cs:331 at FishNet.Transporting.NetworkReaderLoop.Update () [0x00001] in C:\Projects\SpellCurve\Assets\FishNet\Runtime\Transporting\NetworkReaderLoop.cs:28 `

Expected behavior To not get an error on client start

Screenshot Player Prefab image

MrPorky commented 1 year ago

Tis seams to happen because of 'InstanceFinder.TimeManager.OnTick += TimeManager_OnTick;' in awake

FirstGearGames commented 1 year ago

Perhaps you are running replicate in update? I wonder if perhaps it does this when the localtick is 0. I'll try to replicate to see if this could be a problem.

zalkanorr commented 1 year ago

As I was writing this comment, I saw that the only difference with the NetworkManager screenshot provided, was that I was not using PlayerSpawner (I'm still not able to find it in the docs?). I was using a "custom" spawn, using "InstanceFinder.ServerManager.Spawn". When I switched to PlayerSpawner, this error stopped occuring.

Here's the comment I was writing before:

I seem to stumble across the same error. I specifically hook the "base.TimeManager.OnTick" event on "OnNetworkStart" and unhook on "OnNetworkEnd". I've tried to use "InstanceFinder.TimeManager.OnTick" on Awake/Destroy, but with the same outcome.

Steps:

I'm using Unity 2021.3.21f - Metal On a Macbook Pro M1 2020

EDIT:

Alright, kinda embarassing, but I'm going to say this publicly for the sake of bug fixing.

I migrated to Fishy from netcode within a day in a relatively large project (for a solo dev). In netcode for a player identification I was using the clientId.

So, I brainfarted, and for example, on "OnClientLoadedStartScenes", instead of using the NetworkConnection param immediatelly, I was using the connection's clientId to fetch the NetworkConnection from "InstanceFinder.ClientManager.Clients" (I know...)

Here's how the function looked like:

private NetworkConnection GetNetworkConnectionFromClientId(int clientId)
{
    if (!InstanceFinder.IsHost) return null;

    foreach (var networkConnection in InstanceFinder.ClientManager.Clients)
    {
        if (networkConnection.Value.ClientId == clientId)
        {
            print ($"LobbyManager->GetNetworkConnectionFromClientId->found {clientId}");

            return networkConnection.Value;
        }
    }

    return null;
}

So, actually this worked fine without the prediction, and in other parts of code when using rpc. Meaning the networkConnection was actually found. But when I added the prediction, this error started to pop up. Until I wrote this issue and realized that using the NetworkConnection immediately made the error go away.

Don't know if this will help with anything, I'll drop the LobbyManager spawn example to simulate if needed. Here's the abomination of a code (Sorry for your eyes, I was tired)

private void SceneManager_OnClientLoadedStartScenes(NetworkConnection conn, bool asServer)
{
    if (InstanceFinder.IsHost && InstanceFinder.ClientManager.Connection.ClientId != conn.ClientId)
    {
        GameObject PrefabInstance = Instantiate(playerPrefab);

        var networkConnection = GetNetworkConnectionFromClientId(conn.ClientId);
        InstanceFinder.ServerManager.Spawn(PrefabInstance, networkConnection);
    }
}