using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; using System; using UnityEngine.SceneManagement; namespace ES3Internal { [System.Serializable] [DisallowMultipleComponent] public abstract class ES3ReferenceMgrBase : MonoBehaviour { internal object _lock = new object(); public const string referencePropertyName = "_ES3Ref"; private static ES3ReferenceMgrBase _current = null; private static HashSet mgrs = new HashSet(); #if UNITY_EDITOR protected static bool isEnteringPlayMode = false; static readonly HideFlags[] invalidHideFlags = new HideFlags[] { HideFlags.DontSave, HideFlags.DontSaveInBuild, HideFlags.DontSaveInEditor, HideFlags.HideAndDontSave }; #endif #if !UNITY_EDITOR [NonSerialized] #endif public List excludeObjects = new List(); private static System.Random rng; [HideInInspector] public bool openPrefabs = false; // Whether the prefab list should be open in the Editor. public List prefabs = new List(); public static ES3ReferenceMgrBase Current { get { // If the reference manager hasn't been assigned, or we've got a reference to a manager in a different scene which isn't marked as DontDestroyOnLoad, look for this scene's manager. if (_current == null /*|| (_current.gameObject.scene.buildIndex != -1 && _current.gameObject.scene != SceneManager.GetActiveScene())*/) { ES3ReferenceMgrBase mgr = GetManagerFromScene(SceneManager.GetActiveScene()); if(mgr != null) mgrs.Add(_current = mgr); } return _current; } } public static ES3ReferenceMgrBase GetManagerFromScene(Scene scene) { // This has been removed as isLoaded is false during the initial Awake(). /*if (!scene.isLoaded) return null;*/ // If we're in DontDestroyOnLoad, there won't be an Easy Save 3 Manager in this scene, so find any manager. if (scene.buildIndex == -1) { for (int i = 0; i < SceneManager.sceneCount; i++) { var loadedScene = SceneManager.GetSceneAt(i); // Ensure that the scene exists and it's not DontDestroyOnLoad (to avoid an infinte loop) if (loadedScene != null && loadedScene.buildIndex != -1) { var manager = GetManagerFromScene(loadedScene); if (manager != null) return manager; } } return null; } // Check whether the mgr is already in the mgr list. foreach (var addedMgr in mgrs) if (addedMgr != null && addedMgr.gameObject.scene == scene) return addedMgr; GameObject[] roots; try { roots = scene.GetRootGameObjects(); } catch { return null; } ES3ReferenceMgr mgr = null; // First, look for Easy Save 3 Manager in the top-level. foreach (var root in roots) { if (root.name == "Easy Save 3 Manager") { mgr = root.GetComponent(); break; } } // If the user has moved or renamed the Easy Save 3 Manager, we need to perform a deep search. if (mgr == null) foreach (var root in roots) if ((mgr = root.GetComponentInChildren()) != null) break; return mgr; } public bool IsInitialised { get { return idRef.Count > 0; } } [SerializeField] public ES3IdRefDictionary idRef = new ES3IdRefDictionary(); private ES3RefIdDictionary _refId = null; public ES3RefIdDictionary refId { get { if (_refId == null) { _refId = new ES3RefIdDictionary(); // Populate the reverse dictionary with the items from the normal dictionary. foreach (var kvp in idRef) if (kvp.Value != null) _refId[kvp.Value] = kvp.Key; } return _refId; } set { _refId = value; } } public ES3GlobalReferences GlobalReferences { get { return ES3GlobalReferences.Instance; } } // Reset static variables to handle disabled domain reloading. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void Init() { _current = null; mgrs = new HashSet(); #if UNITY_EDITOR isEnteringPlayMode = false; #endif rng = null; } internal void Awake() { if (_current != null && _current != this) { var existing = _current; /* We intentionally use Current rather than _current here, as _current may contain a reference to a manager in another scene, * but Current only returns the Manager for the active scene. */ if (Current != null) { RemoveNullValues(); //existing.Merge(this); //Destroy(this); _current = existing; // Undo the call to Current, which may have set it to NULL. } } else _current = this; mgrs.Add(this); } private void OnDestroy() { if (_current == this) _current = null; mgrs.Remove(this); } // Merges two managers, not allowing any clashes of IDs public void Merge(ES3ReferenceMgrBase otherMgr) { foreach (var kvp in otherMgr.idRef) Add(kvp.Value, kvp.Key); } public long Get(UnityEngine.Object obj) { if (!mgrs.Contains(this)) mgrs.Add(this); foreach (var mgr in mgrs) { if (mgr == null) continue; if (obj == null) return -1; long id; if (mgr.refId.TryGetValue(obj, out id)) return id; } return -1; } internal UnityEngine.Object Get(long id, Type type, bool suppressWarnings=false) { if (!mgrs.Contains(this)) mgrs.Add(this); foreach (var mgr in mgrs) { if (mgr == null) continue; if (id == -1) return null; UnityEngine.Object obj; if (mgr.idRef.TryGetValue(id, out obj)) { if (obj == null) // If obj has been marked as destroyed but not yet destroyed, don't return it. return null; return obj; } } if (GlobalReferences != null) { var globalRef = GlobalReferences.Get(id); if (globalRef != null) return globalRef; } if(type != null) ES3Debug.LogWarning("Reference for " + type + " with ID " + id + " could not be found in Easy Save's reference manager. See the Saving and Loading References guide for more information.", this); else ES3Debug.LogWarning("Reference with ID " + id + " could not be found in Easy Save's reference manager. See the Saving and Loading References guide for more information.", this); return null; } public UnityEngine.Object Get(long id, bool suppressWarnings = false) { return Get(id, null, suppressWarnings); } public ES3Prefab GetPrefab(long id, bool suppressWarnings = false) { if (!mgrs.Contains(this)) mgrs.Add(this); foreach (var mgr in mgrs) { if (mgr == null) continue; foreach (var prefab in mgr.prefabs) if (prefab != null && prefab.prefabId == id) return prefab; } if (!suppressWarnings) ES3Debug.LogWarning("Prefab with ID " + id + " could not be found in Easy Save's reference manager. Try pressing the Refresh References button on the ES3ReferenceMgr Component of the Easy Save 3 Manager in your scene, or exit play mode and right-click the prefab and select Easy Save 3 > Add Reference(s) to Manager.", this); return null; } public long GetPrefab(ES3Prefab prefabToFind, bool suppressWarnings = false) { if (!mgrs.Contains(this)) mgrs.Add(this); foreach (var mgr in mgrs) { if (mgr == null) continue; foreach (var prefab in prefabs) if (prefab == prefabToFind) return prefab.prefabId; } if (!suppressWarnings) ES3Debug.LogWarning("Prefab with name " + prefabToFind.name + " could not be found in Easy Save's reference manager. Try pressing the Refresh References button on the ES3ReferenceMgr Component of the Easy Save 3 Manager in your scene, or exit play mode and right-click the prefab and select Easy Save 3 > Add Reference(s) to Manager.", prefabToFind); return -1; } public long Add(UnityEngine.Object obj) { if (obj == null) return -1; if (!CanBeSaved(obj)) return -1; long id; // If it already exists in the list, do nothing. if (refId.TryGetValue(obj, out id)) return id; if (GlobalReferences != null) { id = GlobalReferences.GetOrAdd(obj); if (id != -1) { Add(obj, id); return id; } } lock (_lock) { // Add the reference to the Dictionary. id = GetNewRefID(); return Add(obj, id); } } public long Add(UnityEngine.Object obj, long id) { if (obj == null) return -1; if (!CanBeSaved(obj)) return -1; // If the ID is -1, auto-generate an ID. if (id == -1) id = GetNewRefID(); // Add the reference to the Dictionary. lock (_lock) { idRef[id] = obj; if(obj != null) refId[obj] = id; } return id; } public bool AddPrefab(ES3Prefab prefab) { if (!prefabs.Contains(prefab)) { prefabs.Add(prefab); return true; } return false; } public void Remove(UnityEngine.Object obj) { if (!mgrs.Contains(this)) mgrs.Add(this); foreach (var mgr in mgrs) { if (mgr == null) continue; // Only remove from this manager if we're in the Editor. if (!Application.isPlaying && mgr != this) continue; lock (mgr._lock) { mgr.refId.Remove(obj); // There may be multiple references with the same ID, so remove them all. foreach (var item in mgr.idRef.Where(kvp => kvp.Value == obj).ToList()) mgr.idRef.Remove(item.Key); } } } public void Remove(long referenceID) { foreach (var mgr in mgrs) { if (mgr == null) continue; lock (mgr._lock) { mgr.idRef.Remove(referenceID); // There may be multiple references with the same ID, so remove them all. foreach (var item in mgr.refId.Where(kvp => kvp.Value == referenceID).ToList()) mgr.refId.Remove(item.Key); } } } public void RemoveNullValues() { var nullKeys = idRef.Where(pair => pair.Value == null).Select(pair => pair.Key).ToList(); foreach (var key in nullKeys) idRef.Remove(key); } public void RemoveNullOrInvalidValues() { var nullKeys = idRef.Where(pair => pair.Value == null || !CanBeSaved(pair.Value) || excludeObjects.Contains(pair.Value)).Select(pair => pair.Key).ToList(); foreach (var key in nullKeys) idRef.Remove(key); if (GlobalReferences != null) GlobalReferences.RemoveInvalidKeys(); } public void Clear() { lock (_lock) { refId.Clear(); idRef.Clear(); } } public bool Contains(UnityEngine.Object obj) { return refId.ContainsKey(obj); } public bool Contains(long referenceID) { return idRef.ContainsKey(referenceID); } public void ChangeId(long oldId, long newId) { idRef.ChangeKey(oldId, newId); // Empty the refId so it has to be refreshed. refId = null; } internal static long GetNewRefID() { if (rng == null) rng = new System.Random(); byte[] buf = new byte[8]; rng.NextBytes(buf); long longRand = BitConverter.ToInt64(buf, 0); return (System.Math.Abs(longRand % (long.MaxValue - 0)) + 0); } /*#if UNITY_EDITOR public static HashSet CollectDependenciesLegacy(UnityEngine.Object obj, HashSet dependencies = null, int depth = int.MinValue) { return CollectDependenciesLegacy(new UnityEngine.Object[] { obj }, dependencies, depth); } //Collects all top-level dependencies of an object. //For GameObjects, it will traverse all children. //For Components or ScriptableObjects, it will get all serialisable UnityEngine.Object fields/properties as dependencies. public static HashSet CollectDependenciesLegacy(UnityEngine.Object[] objs, HashSet dependencies = null, int depth = int.MinValue) { if (depth == int.MinValue) depth = ES3Settings.defaultSettingsScriptableObject.collectDependenciesDepth; if (depth < 0) return dependencies; if (dependencies == null) dependencies = new HashSet(); foreach (var obj in objs) { if (obj == null) continue; var type = obj.GetType(); // Skip types which don't need processing if (type == typeof(ES3ReferenceMgr) || type == typeof(ES3AutoSaveMgr) || type == typeof(ES3AutoSave) || type == typeof(ES3InspectorInfo)) continue; // Add the prefab to the manager but don't process it. We'll use this to work out what prefabs to add to the prefabs list later. if (type == typeof(ES3Prefab)) { dependencies.Add(obj); continue; } // If it's a GameObject, get the GameObject's Components and collect their dependencies. if (type == typeof(GameObject)) { var go = (GameObject)obj; // If we've not already processed this GameObject ... if (dependencies.Add(go)) { // Get the dependencies of each Component in the GameObject. CollectDependenciesLegacy(go.GetComponents(), dependencies, depth - 1); // Get the dependencies of each child in the GameObject. foreach (Transform child in go.transform) CollectDependenciesLegacy(child.gameObject, dependencies, depth); // Don't decrement child, as we consider this a top-level object. } } // Else if it's a Component or ScriptableObject, add the values of any UnityEngine.Object fields as dependencies. else CollectDependenciesFromFieldsLegacy(obj, dependencies, depth - 1); } return dependencies; } private static void CollectDependenciesFromFieldsLegacy(UnityEngine.Object obj, HashSet dependencies, int depth) { // If we've already collected dependencies for this, do nothing. if (!dependencies.Add(obj)) return; if (depth == int.MinValue) depth = ES3Settings.defaultSettingsScriptableObject.collectDependenciesDepth; if (depth < 0) return; var type = obj.GetType(); if (isEnteringPlayMode && type == typeof(UnityEngine.UI.Text)) return; try { // SerializedObject is expensive, so for known classes we manually gather references. if (type == typeof(Animator) || obj is Transform || type == typeof(CanvasRenderer) || type == typeof(Mesh) || type == typeof(AudioClip) || type == typeof(Rigidbody) || obj is HorizontalOrVerticalLayoutGroup) return; if(obj is Texture) { // This ensures that Sprites which are children of the Texture are also added. In the Editor you would otherwise need to expand the Texture to add the Sprite. foreach(var dependency in UnityEditor.AssetDatabase.LoadAllAssetsAtPath(UnityEditor.AssetDatabase.GetAssetPath(obj))) if (dependency != obj) dependencies.Add(dependency); } if (obj is Graphic) { var m = (Graphic)obj; dependencies.Add(m.material); dependencies.Add(m.defaultMaterial); dependencies.Add(m.mainTexture); if (type == typeof(Text)) { var text = (Text)obj; dependencies.Add(text.font); } else if (type == typeof(Image)) { var img = (Image)obj; dependencies.Add(img.sprite); } return; } if (type == typeof(Mesh)) { if (UnityEditor.AssetDatabase.Contains(obj)) dependencies.Add(obj); return; } if (type == typeof(Material)) { var material = (Material)obj; var shader = material.shader; if (shader != null) { dependencies.Add(material.shader); #if UNITY_2019_3_OR_NEWER for (int i = 0; i < shader.GetPropertyCount(); i++) if (shader.GetPropertyType(i) == UnityEngine.Rendering.ShaderPropertyType.Texture) dependencies.Add(material.GetTexture(shader.GetPropertyName(i))); } #endif return; } if (type == typeof(MeshFilter)) { dependencies.Add(((MeshFilter)obj).sharedMesh); return; } if (type == typeof(MeshCollider)) { var mc = (MeshCollider)obj; dependencies.Add(mc.sharedMesh); dependencies.Add(mc.sharedMaterial); dependencies.Add(mc.attachedRigidbody); return; } if (type == typeof(Camera)) { var c = (Camera)obj; dependencies.Add(c.targetTexture); return; } if (type == typeof(SkinnedMeshRenderer)) dependencies.Add(((SkinnedMeshRenderer)obj).sharedMesh); // Don't return. Let this fall through to the if(obj is renderer) call. else if (type == typeof(SpriteRenderer)) dependencies.Add(((SpriteRenderer)obj).sprite); // Don't return. Let this fall through to the if(obj is renderer) call. else if (type == typeof(ParticleSystemRenderer)) dependencies.Add(((ParticleSystemRenderer)obj).mesh); // Don't return. Let this fall through to the if(obj is renderer) call. if (obj is Renderer) { var renderer = (Renderer)obj; foreach (var material in renderer.sharedMaterials) CollectDependenciesFromFieldsLegacy(material, dependencies, depth - 1); return; } } catch { } var so = new UnityEditor.SerializedObject(obj); if (so == null) return; var property = so.GetIterator(); if (property == null) return; // Iterate through each of this object's properties. while (property.NextVisible(true)) { try { // If it's an array which contains UnityEngine.Objects, add them as dependencies. if (property.isArray && property.propertyType != UnityEditor.SerializedPropertyType.String) { for (int i = 0; i < property.arraySize; i++) { var element = property.GetArrayElementAtIndex(i); // If the array contains UnityEngine.Object types, add them to the dependencies. if (element.propertyType == UnityEditor.SerializedPropertyType.ObjectReference) { var elementValue = element.objectReferenceValue; var elementType = elementValue.GetType(); // If it's a GameObject, use CollectDependencies so that Components are also added. if (elementType == typeof(GameObject)) CollectDependenciesLegacy(elementValue, dependencies, depth - 1); else CollectDependenciesFromFieldsLegacy(elementValue, dependencies, depth - 1); } // Otherwise this array does not contain UnityEngine.Object types, so we should stop. else break; } } // Else if it's a normal UnityEngine.Object field, add it. else if (property.propertyType == UnityEditor.SerializedPropertyType.ObjectReference) { var propertyValue = property.objectReferenceValue; if (propertyValue == null) continue; // If it's a GameObject, use CollectDependencies so that Components are also added. if (propertyValue.GetType() == typeof(GameObject)) CollectDependenciesLegacy(propertyValue, dependencies, depth - 1); else CollectDependenciesFromFieldsLegacy(propertyValue, dependencies, depth - 1); } } catch { } } } // Called in the Editor when this Component is added. private void Reset() { // Ensure that Component can only be added by going to Assets > Easy Save 3 > Add Manager to Scene. if (gameObject.name != "Easy Save 3 Manager") { UnityEditor.EditorUtility.DisplayDialog("Cannot add ES3ReferenceMgr directly", "Please go to 'Tools > Easy Save 3 > Add Manager to Scene' to add an Easy Save 3 Manager to your scene.", "Ok"); DestroyImmediate(this); } } #endif*/ internal static bool CanBeSaved(UnityEngine.Object obj) { #if UNITY_EDITOR if (obj == null) return true; foreach (var flag in invalidHideFlags) if ((obj.hideFlags & flag) != 0 && obj.hideFlags != HideFlags.HideInHierarchy && obj.hideFlags != HideFlags.HideInInspector && obj.hideFlags != HideFlags.NotEditable) if (!(obj is Mesh || obj is Material)) return false; // Exclude the Easy Save 3 Manager, and all components attached to it. if (obj.name == "Easy Save 3 Manager") return false; #endif return true; } } [System.Serializable] public class ES3IdRefDictionary : ES3SerializableDictionary { protected override bool KeysAreEqual(long a, long b) { return a == b; } protected override bool ValuesAreEqual(UnityEngine.Object a, UnityEngine.Object b) { return a == b; } } [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Serializable] public class ES3RefIdDictionary : ES3SerializableDictionary { protected override bool KeysAreEqual(UnityEngine.Object a, UnityEngine.Object b) { return a == b; } protected override bool ValuesAreEqual(long a, long b) { return a == b; } } }