using System; using System.Collections.Generic; using System.Text; using UnityEngine; namespace Unity.Netcode { /// /// A class that represents the runtime aspect of network prefabs. /// This class contains processed prefabs from the NetworkPrefabsList, as /// well as additional modifications (additions and removals) made at runtime. /// [Serializable] public class NetworkPrefabs { /// /// Edit-time scripted object containing a list of NetworkPrefabs. /// /// /// This field can be null if no prefabs are pre-configured. /// Runtime usages of should not depend on this edit-time field for execution. /// [SerializeField] public List NetworkPrefabsLists = new List(); /// /// This dictionary provides a quick way to check and see if a NetworkPrefab has a NetworkPrefab override. /// Generated at runtime and OnValidate /// [NonSerialized] public Dictionary NetworkPrefabOverrideLinks = new Dictionary(); /// /// This is used for the legacy way of spawning NetworkPrefabs with an override when manually instantiating and spawning. /// To handle multiple source NetworkPrefab overrides that all point to the same target NetworkPrefab use /// /// or /// [NonSerialized] public Dictionary OverrideToNetworkPrefab = new Dictionary(); public IReadOnlyList Prefabs => m_Prefabs; [NonSerialized] private List m_Prefabs = new List(); [NonSerialized] private List m_RuntimeAddedPrefabs = new List(); private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) { if (AddPrefabRegistration(networkPrefab)) { // Don't add this to m_RuntimeAddedPrefabs // This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there. m_Prefabs.Add(networkPrefab); } } private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) { m_Prefabs.Remove(networkPrefab); } ~NetworkPrefabs() { Shutdown(); } /// /// Deregister from add and remove events /// Clear the list /// internal void Shutdown() { foreach (var list in NetworkPrefabsLists) { list.OnAdd -= AddTriggeredByNetworkPrefabList; list.OnRemove -= RemoveTriggeredByNetworkPrefabList; } } /// /// Processes the if one is present for use during runtime execution, /// else processes . /// public void Initialize(bool warnInvalid = true) { m_Prefabs.Clear(); foreach (var list in NetworkPrefabsLists) { list.OnAdd += AddTriggeredByNetworkPrefabList; list.OnRemove += RemoveTriggeredByNetworkPrefabList; } NetworkPrefabOverrideLinks.Clear(); OverrideToNetworkPrefab.Clear(); var prefabs = new List(); if (NetworkPrefabsLists.Count != 0) { foreach (var list in NetworkPrefabsLists) { foreach (var networkPrefab in list.PrefabList) { prefabs.Add(networkPrefab); } } } m_Prefabs = new List(); List removeList = null; if (warnInvalid) { removeList = new List(); } foreach (var networkPrefab in prefabs) { if (AddPrefabRegistration(networkPrefab)) { m_Prefabs.Add(networkPrefab); } else { removeList?.Add(networkPrefab); } } foreach (var networkPrefab in m_RuntimeAddedPrefabs) { if (AddPrefabRegistration(networkPrefab)) { m_Prefabs.Add(networkPrefab); } else { removeList?.Add(networkPrefab); } } // Clear out anything that is invalid or not used if (removeList?.Count > 0) { if (NetworkLog.CurrentLogLevel <= LogLevel.Error) { var sb = new StringBuilder("Removing invalid prefabs from Network Prefab registration: "); sb.Append(string.Join(", ", removeList)); NetworkLog.LogWarning(sb.ToString()); } } } /// /// Add a new NetworkPrefab instance to the list /// /// /// The framework does not synchronize this list between clients. Any runtime changes must be handled manually. /// /// Any modifications made here are not persisted. Permanent configuration changes should be done /// through the scriptable object property. /// public bool Add(NetworkPrefab networkPrefab) { if (AddPrefabRegistration(networkPrefab)) { m_Prefabs.Add(networkPrefab); m_RuntimeAddedPrefabs.Add(networkPrefab); return true; } return false; } /// /// Remove a NetworkPrefab instance from the list /// /// /// The framework does not synchronize this list between clients. Any runtime changes must be handled manually. /// /// Any modifications made here are not persisted. Permanent configuration changes should be done /// through the scriptable object property. /// public void Remove(NetworkPrefab prefab) { if (prefab == null) { throw new ArgumentNullException(nameof(prefab)); } m_Prefabs.Remove(prefab); m_RuntimeAddedPrefabs.Remove(prefab); OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash); NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash); } /// /// Remove a NetworkPrefab instance with matching from the list /// /// /// The framework does not synchronize this list between clients. Any runtime changes must be handled manually. /// /// Any modifications made here are not persisted. Permanent configuration changes should be done /// through the scriptable object property. /// public void Remove(GameObject prefab) { if (prefab == null) { throw new ArgumentNullException(nameof(prefab)); } for (int i = 0; i < m_Prefabs.Count; i++) { if (m_Prefabs[i].Prefab == prefab) { Remove(m_Prefabs[i]); return; } } for (int i = 0; i < m_RuntimeAddedPrefabs.Count; i++) { if (m_RuntimeAddedPrefabs[i].Prefab == prefab) { Remove(m_RuntimeAddedPrefabs[i]); return; } } } /// /// Check if the given GameObject is present as a prefab within the list /// /// The prefab to check /// Whether or not the prefab exists public bool Contains(GameObject prefab) { for (int i = 0; i < m_Prefabs.Count; i++) { // Check both values as Prefab and be different than SourcePrefabToOverride if (m_Prefabs[i].Prefab == prefab || m_Prefabs[i].SourcePrefabToOverride == prefab) { return true; } } return false; } /// /// Check if the given NetworkPrefab is present within the list /// /// The prefab to check /// Whether or not the prefab exists public bool Contains(NetworkPrefab prefab) { for (int i = 0; i < m_Prefabs.Count; i++) { if (m_Prefabs[i].Equals(prefab)) { return true; } } return false; } /// /// Configures for the given /// private bool AddPrefabRegistration(NetworkPrefab networkPrefab) { if (networkPrefab == null) { return false; } // Safeguard validation check since this method is called from outside of NetworkConfig and we can't control what's passed in. if (!networkPrefab.Validate()) { return false; } uint source = networkPrefab.SourcePrefabGlobalObjectIdHash; uint target = networkPrefab.TargetPrefabGlobalObjectIdHash; // Make sure the prefab isn't already registered. if (NetworkPrefabOverrideLinks.ContainsKey(source)) { var networkObject = networkPrefab.Prefab.GetComponent(); // This should never happen, but in the case it somehow does log an error and remove the duplicate entry Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!"); return false; } // If we don't have an override configured, registration is simple! if (networkPrefab.Override == NetworkPrefabOverride.None) { NetworkPrefabOverrideLinks.Add(source, networkPrefab); return true; } switch (networkPrefab.Override) { case NetworkPrefabOverride.Prefab: case NetworkPrefabOverride.Hash: { NetworkPrefabOverrideLinks.Add(source, networkPrefab); if (!OverrideToNetworkPrefab.ContainsKey(target)) { OverrideToNetworkPrefab.Add(target, source); } } break; } return true; } } }