using System; using System.Collections.Generic; using UnityEngine; using VRC.SDK3.Components; using VRC.SDKBase; namespace VRC.SDK3.ClientSim { /// /// System that keeps track of all synced objects. Handles changing ownership when players leave /// and also checks for if position synced objects fall below the respawn height. /// /// /// Listens to Events: /// - ClientSimOnPlayerLeftEvent /// [AddComponentMenu("")] public class ClientSimSyncedObjectManager : ClientSimBehaviour, IClientSimSyncedObjectManager, IDisposable { // Collection of all synced objects initialized in the scene. private readonly ClientSimObjectCollection _syncedObjects = new ClientSimObjectCollection(); // Collection of all position synced objects initialized in the scene. private readonly ClientSimObjectCollection _positionSyncedObjects = new ClientSimObjectCollection(); // Collection of all synced playerObjects initialized in the scene. private readonly ClientSimObjectCollection _playerObjects = new ClientSimObjectCollection(); // TODO add generic system for saving synced data and restoring it to simulate late joining into a world. // TODO add system to manage udon synced data private IClientSimEventDispatcher _eventDispatcher; private IClientSimSceneManager _sceneManager; private IClientSimPlayerManager _playerManager; public void Initialize( IClientSimEventDispatcher eventDispatcher, IClientSimSceneManager sceneManager, IClientSimPlayerManager playerManager) { _eventDispatcher = eventDispatcher; _sceneManager = sceneManager; _playerManager = playerManager; _eventDispatcher.Subscribe(OnPlayerLeft); } private void OnDestroy() { Dispose(); } public void Dispose() { _eventDispatcher?.Unsubscribe(OnPlayerLeft); } public void InitializeObjectSync(VRCObjectSync sync) { // Only allow one object sync helper per object. if (sync.TryGetComponent(out ClientSimObjectSyncHelper syncHelper)) { syncHelper.Initialize(sync,this); } else { ClientSimObjectSyncHelper helper = sync.gameObject.AddComponent(); helper.Initialize(sync,this); } } public void InitializeObjectPool(VRCObjectPool objectPool) { objectPool.gameObject.AddComponent().Initialize(objectPool, this); } private void LateUpdate() { ProcessPositionSyncedObjects(); } private void ProcessPositionSyncedObjects() { _positionSyncedObjects.ProcessAddedAndRemovedObjects(); // TODO space this out so that there are only x number per frame instead of all every time List objsToDestroy = new List(); foreach (IClientSimPositionSyncable sync in _positionSyncedObjects.GetObjects()) { if (sync == null) { _positionSyncedObjects.ShouldVerifyObjects(); continue; } if (!sync.SyncPosition) { continue; } // VRChatBug: The following method will enforce users to use VRCObjectSync's methods for setting // useGravity and isKinematic, but the current SDK does not support the hook to allow this. // Verify Sync properties, eg check if useGravity and isKinematic is properly set. // sync.UpdatePositionSync(); // Verify if the object is below respawn. Transform syncTransform = sync.GetTransform(); if (syncTransform.position.y < _sceneManager.GetRespawnHeight()) { if (_sceneManager.ShouldObjectsDestroyAtRespawnHeight()) { objsToDestroy.Add(syncTransform.gameObject); } else { sync.Respawn(); } } } foreach (var obj in objsToDestroy) { Destroy(obj); } } #region ClientSim Events // Handle updating object ownership for all objects the leaving player previously owned. private void OnPlayerLeft(ClientSimOnPlayerLeftEvent leftEvent) { VRCPlayerApi leftPlayer = leftEvent.player; int leftPlayerId = leftPlayer.playerId; VRCPlayerApi masterPlayer = _playerManager.GetMaster(); if (masterPlayer == null) { return; } _syncedObjects.ProcessAddedAndRemovedObjects(); foreach (IClientSimSyncable sync in _syncedObjects.GetObjects()) { if (sync == null) { continue; } if (sync is Component syncComp) { if(syncComp == null) continue; GameObject syncObj = syncComp.gameObject; if (Networking.GetOwner(syncObj)?.playerId == leftPlayerId) { Networking.SetOwner(masterPlayer, syncObj); } } else { if (sync.GetOwner() == leftPlayerId) { sync.SetOwner(masterPlayer.playerId); } } } } #endregion #region IClientSimSyncedObjectManager public void AddSyncedObject(IClientSimSyncable sync) { _syncedObjects.AddObject(sync); _syncedObjects.ProcessAddedAndRemovedObjects(); if (sync is IClientSimPositionSyncable posSync && posSync.SyncPosition) { _positionSyncedObjects.AddObject(posSync); _positionSyncedObjects.ProcessAddedAndRemovedObjects(); } if((sync as MonoBehaviour).GetComponentInParent() != null) { _playerObjects.AddObject(sync); _playerObjects.ProcessAddedAndRemovedObjects(); } } public void RemoveSyncedObject(IClientSimSyncable sync) { _syncedObjects.RemoveObject(sync); _syncedObjects.ProcessAddedAndRemovedObjects(); if (sync is IClientSimPositionSyncable posSync && posSync.SyncPosition) { _positionSyncedObjects.RemoveObject(posSync); _positionSyncedObjects.ProcessAddedAndRemovedObjects(); } if((sync as MonoBehaviour).GetComponentInParent() != null) { _playerObjects.RemoveObject(sync); _playerObjects.ProcessAddedAndRemovedObjects(); } } #endregion } }