Skip to content

NetworkVariable.CheckDirtyState() doesn't sync latest changes if already dirty #3348

@spaceapplesoft

Description

@spaceapplesoft

Description

Updating collection in a NetworkVariable and then calling CheckDirtyState() doesn't sync latest changes if it's already dirty.

Reproduce Steps

On host (didn't test actual clients)

  1. Update collection
  2. Call CheckDirtyState()
  3. Update collection again
  4. CheckDirtyState()

Actual Outcome

On client, NetworkVariable .OnValueChanged is called, but collection has only the first change

Expected Outcome

On client, NetworkVariable .OnValueChanged is called and collection has the latest changes

Environment

  • OS: Windows 11
  • Unity Version: 6000.0.25f1
  • Netcode Version: 2.2.0

Additional Context

I had a stats system, which was represented by a simple Dictionary<int, int>.
When I equip an item, it modifies the stats and calls the CheckDirtyState - client gets notified with new value and everything's good.
But if I immediately add another item, which modifies stats some more, and call CheckDirtyState - new value does not get sent unless I do the forceCheck.

Documentation states that CheckDirtyState exits early if it's already dirty. While doing so, it doesn't take the latest changes. I guess the reason for this early exit can be interpreted in two ways:

  1. If something is dirty, there's no need to check if it's dirty again. Then, at the end of the frame or whenever serialization runs - actual changes would be calculated and synced with the client.
  2. A way to optimize calling this multiple times

If intended reasoning is 1 - then there's a bug, if 2 - forceCheck should default to true, and "false" becomes logically unnecessary. I could elaborate on that.

Here's an example to test this behaviour:

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;

public class TestBehaviour: NetworkBehaviour
{
	public NetworkVariable<Dictionary<int,int>> _stats = new NetworkVariable<Dictionary<int, int>>(new Dictionary<int, int>());

	private void Awake()
	{
	}

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

		if (IsClient)
		{
			_stats.OnValueChanged += OnValuesChanged;
		}

		if (IsServer)
		{
			StartCoroutine(TriggerChanges());
		}
	}

	private IEnumerator TriggerChanges()
	{
		yield return new WaitForSeconds(1f);

		_stats.Value[0] = 1;

		_stats.CheckDirtyState();

		_stats.Value[0] = 2;

		_stats.CheckDirtyState(); // If I add true to this, everything works as expected
	}

	private void OnValuesChanged(Dictionary<int, int> previousValue, Dictionary<int, int> newValue)
	{
		Debug.Log($"Values changed on client: {(_stats.Value.ContainsKey(0) ? _stats.Value[0].ToString() : "N/A")}");
	}
}

In this example, only this is printed:
Values changed on client: 1

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:supportQuestions or other support

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions