Udons that are inactive at the time of world loading will not be synchronized when they become active
I first looked at the state immediately after Join.
  1. build the world by deactivating the Udon with one int type Udonsync variable
  2. the owner (playerID = 1) activates the synchronization object and counts the variable +1
  3. nothing happens under non-owner player2 (playerID = 2)
  4. player2 activates the synchronization object and then the owner sets the count of the variable to 2.
  5. the value is synchronized for the first time to the synchronization object of player 2 and the count is set to 2.
Then we examined the state of the object once it was activated.
  1. the synchronization object of player 2 is deactivated.
  2. the owner sets the variable count +1 twice.
  3. nothing happens under player 2.
  4. when player 2 activates the synchronization object, the synchronization events during the period of inactivity are executed consecutively and the count goes to 3 and then to 4.
Finally, the state of the later-joiner is examined.
  1. In player 3's client, nothing happens immediately after Join because the Udon object is inactive.
  2. nothing happens when player 3 activates the object.
  3. when the owner sets the count of a variable to 5, the value of 5 is suddenly synced to player 3.
If the Udon is active at the time of loading the world, the later-joiner will also be synchronized with the latest values immediately after the join. The owner does not need to do anything at this time.
Translated with www.DeepL.com/Translator (free version)
Test code:
using System;
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
using TMPro;
using VRC.Udon.Common;
public class LaterJoinerTest : UdonSharpBehaviour
{
[SerializeField]
TextMeshProUGUI log;
[UdonSynced, FieldChangeCallback(nameof(TestValue))]
int _testValue = 0;
public int TestValue
{
get => _testValue;
set
{
log.text += DateTime.Now.ToLongTimeString() + " OnValueChanged: new value = " + value.ToString() + ", old value = " + _testValue.ToString() + "\n";
_testValue = value;
}
}
void Start()
{
log.text += DateTime.Now.ToLongTimeString() + " Start: playerID = " + Networking.LocalPlayer.playerId.ToString() + " value = " + TestValue.ToString() + "\n";
}
private void OnEnable()
{
log.text += DateTime.Now.ToLongTimeString() + " OnEnable: value = " + TestValue.ToString() + "\n";
}
private void OnDisable()
{
log.text += DateTime.Now.ToLongTimeString() + " OnDisable: value = " + TestValue.ToString() + "\n";
}
public override void OnPreSerialization()
{
log.text += DateTime.Now.ToLongTimeString() + " OnPreSerializaton: value = " + TestValue.ToString() + "\n";
}
public override void OnPostSerialization(SerializationResult result)
{
log.text += DateTime.Now.ToLongTimeString() + " OnPostSerializaton: value = " + TestValue.ToString() + "\n";
}
public override void OnDeserialization()
{
log.text += DateTime.Now.ToLongTimeString() + " OnDeserializaton: value = " + TestValue.ToString() + "\n";
}
public override void OnOwnershipTransferred(VRCPlayerApi player)
{
log.text += DateTime.Now.ToLongTimeString() + " OnOwnershipTransferred: playerID = " + player.playerId.ToString() + " value = " + TestValue.ToString() + "\n";
}
public override void OnPlayerJoined(VRCPlayerApi player)
{
log.text += DateTime.Now.ToLongTimeString() + " OnPlayerJoined: playerID = " + player.playerId.ToString() + " value = " + TestValue.ToString() + "\n";
}
public override void Interact()
{
if (!Networking.IsOwner(this.gameObject)) { Networking.SetOwner(Networking.LocalPlayer, this.gameObject); }
TestValue++;
RequestSerialization();
log.text += DateTime.Now.ToLongTimeString() + " Interact & RequestSerialization: value = " + TestValue.ToString() + "\n";
}
}