// // /*===============================================================================
// // Copyright (C) 2024 PhantomsXR Ltd. All Rights Reserved.
// //
// // This file is part of the Phantom.XRMOD.PhotonModule.Runtime.
// //
// // The AVPPlatform cannot be copied, distributed, or made available to
// // third-parties for commercial purposes without written permission of PhantomsXR Ltd.
// //
// // Contact info@phantomsxr.com for licensing requests.
// // ===============================================================================*/
#if FUSION2
using System;
using System.Collections.Generic;
using System.Linq;
using Fusion;
using Phantom.XRMOD.ActionNotification.Runtime;
using UnityEngine;
using UnityEngine.Serialization;
namespace Phantom.XRMOD.PhotonModule.Runtime
{
///
/// Adapter to Unity Fusion spawn the network object at runtime.
///
public class BakingObjectProvider : NetworkObjectProviderDefault
{
[Tooltip("The objects to be pooled, leave it empty to pool every Network Object spawned")] [SerializeField]
private List poolableObjects = new List();
private readonly Dictionary> free = new();
// The NetworkObjectBaker class can be reused and is Runner independent.
private static NetworkObjectBaker _BAKER;
private static NetworkObjectBaker Baker => _BAKER ??= new NetworkObjectBaker();
public override NetworkObjectAcquireResult AcquirePrefabInstance(NetworkRunner _runner,
in NetworkPrefabAcquireContext _context, out NetworkObject _result)
{
_result = null;
if (DelayIfSceneManagerIsBusy && _runner.SceneManager.IsBusy)
{
return NetworkObjectAcquireResult.Retry;
}
NetworkObject tmp_Prefab;
try
{
tmp_Prefab = _runner.Prefabs.Load(_context.PrefabId, isSynchronous: _context.IsSynchronous);
}
catch (Exception tmp_Ex)
{
Log.Error($"Failed to load prefab: {tmp_Ex}");
return NetworkObjectAcquireResult.Failed;
}
if (!tmp_Prefab)
{
// this is ok, as long as Fusion does not require the prefab to be loaded immediately;
// if an instance for this prefab is still needed, this method will be called again next update
return _context.IsSynchronous ? NetworkObjectAcquireResult.Failed : NetworkObjectAcquireResult.Retry;
}
_result = InstantiatePrefab(_runner, tmp_Prefab);
if (_result == null) return NetworkObjectAcquireResult.Failed;
var tmp_Go = _result.gameObject;
// Baking is required for the NetworkObject to be valid for spawning.
Baker.Bake(tmp_Go);
// Move the object to the applicable Runner Scene/PhysicsScene/DontDestroyOnLoad
// These implementations exist in the INetworkSceneManager assigned to the runner.
if (_context.DontDestroyOnLoad)
{
_runner.MakeDontDestroyOnLoad(tmp_Go);
}
else
{
_runner.MoveToRunnerScene(tmp_Go);
}
// We are finished. Return the NetworkObject and report success.
_result = tmp_Go.GetComponent();
_runner.Prefabs.AddInstance(_context.PrefabId);
return NetworkObjectAcquireResult.Success;
}
protected override NetworkObject InstantiatePrefab(NetworkRunner _runner, NetworkObject _prefab)
{
if (ShouldPool(_prefab))
{
var tmp_Instance = GetObjectFromPool(_prefab);
tmp_Instance.transform.position = Vector3.zero;
return tmp_Instance;
}
if (CreateUnityFusionNetworkObject(_prefab, out var tmp_InstanceObject))
return tmp_InstanceObject;
return null;
}
protected override void DestroyPrefabInstance(NetworkRunner _runner, NetworkPrefabId _prefabId,
NetworkObject _instance)
{
if (free.TryGetValue(_prefabId, out var tmp_Stack))
{
_instance.gameObject.SetActive(false);
tmp_Stack.Push(_instance);
}
else
{
base.DestroyPrefabInstance(_runner, _prefabId, _instance);
}
}
private NetworkObject GetObjectFromPool(NetworkObject _prefab)
{
NetworkObject tmp_Instance = null;
if (free.TryGetValue(_prefab.NetworkTypeId, out var tmp_Stack))
{
while (tmp_Stack.Count > 0 && tmp_Instance == null)
{
tmp_Instance = tmp_Stack.Pop();
}
}
if (tmp_Instance == null)
tmp_Instance = GetNewInstance(_prefab);
tmp_Instance.gameObject.SetActive(true);
return tmp_Instance;
}
private NetworkObject GetNewInstance(NetworkObject _prefab)
{
if (!CreateUnityFusionNetworkObject(_prefab, out var tmp_Instance)) return null;
if (free.TryGetValue(_prefab.NetworkTypeId, out var tmp_Stack) == false)
{
tmp_Stack = new Stack();
free.Add(_prefab.NetworkTypeId, tmp_Stack);
}
return tmp_Instance;
}
private static bool CreateUnityFusionNetworkObject(NetworkObject _prefab, out NetworkObject _networkObject)
{
_networkObject = null;
// VERY IMPORTANT!!! It will convert the Mono-behaviour(Binder) scripts to Unity fusion adapter.
// Because Network-behaviour CAN NOT add to the game-object at runtime.
var tmp_Results = ActionNotificationCenter.DefaultCenter.PostNotificationWithResult(
nameof(ActionParameterDataType.Instantiate), new InstantiateArgs()
{
Prefab = _prefab.gameObject
});
if (tmp_Results.Count == 0) return false;
if (tmp_Results[0] is not GameObject tmp_SpawnObject) return false;
//NetworkBehaviourBinderManager.DoBind(tmp_SpawnObject.GetComponents().ToList());
return tmp_SpawnObject.TryGetComponent(out _networkObject);
}
private bool ShouldPool(NetworkObject _prefab)
{
return poolableObjects.Count > 0 && IsPoolableObject(_prefab);
}
private bool IsPoolableObject(NetworkObject _networkObject)
{
foreach (var tmp_PoolableObject in poolableObjects)
{
if (_networkObject == tmp_PoolableObject)
{
return true;
}
}
return false;
}
private void OnDestroy()
{
poolableObjects.Clear();
poolableObjects = null;
free.Clear();
_BAKER = null;
}
}
}
#endif