using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using PrefsGUi.Editor;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Pool;
using Object = UnityEngine.Object;
namespace PrefsGUI.Editor
{
///
/// PrefsParamを持つオブジェクトをリストアップする
/// - scene 内
/// - prefab
/// - scriptable object
///
public class PrefsAssetUtility : AssetPostprocessor
{
#region Type Define
public class PrefsHolderComponent
{
public Object component;
public HashSet prefsSet;
}
public class ObjPrefs
{
public readonly Object obj;
public readonly List holders;
public ObjPrefs(Object obj, IEnumerable holders)
{
this.obj = obj;
this.holders = holders.ToList();
}
public IEnumerable PrefsAll => holders.SelectMany(h => h.prefsSet);
public Object GetPrefsParent(PrefsParam prefs)
{
return holders.FirstOrDefault(h => h.prefsSet.Contains(prefs))?.component;
}
}
readonly struct ObjPrefsComparer : IEqualityComparer
{
public bool Equals(ObjPrefs x, ObjPrefs y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return Equals(x.obj, y.obj) && x.holders.SequenceEqual(y.holders, new PrefsHolderComparer());
}
public int GetHashCode(ObjPrefs obj)
{
return HashCode.Combine(obj.obj, obj.holders);
}
}
readonly struct PrefsHolderComparer : IEqualityComparer
{
public bool Equals(PrefsHolderComponent x, PrefsHolderComponent y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return Equals(x.component, y.component) && x.prefsSet.SequenceEqual(y.prefsSet);
}
public int GetHashCode(PrefsHolderComponent obj)
{
return HashCode.Combine(obj.component, obj.prefsSet);
}
}
#endregion
#region AssetPostprocessor
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets,
string[] movedFromAssetPaths)
{
needUpdate = true;
}
#endregion
private static readonly float interval = 3f;
private static float lastUpdateCheckTime;
public static Action onObjPrefsListChanged;
private static List objPrefsList = new();
private static bool needUpdate = true;
public static IEnumerable ObjPrefsList
{
get
{
UpdateObjPrefsIfNeed();
return objPrefsList;
}
}
public static IEnumerable GetObjPrefsList(bool includeAssets)
{
return includeAssets
? ObjPrefsList
: ObjPrefsList.Where(op => op.obj != null && !PrefabUtility.IsPartOfPrefabAsset(op.obj));
}
public static IEnumerable<(PrefsParam prefs, Object obj)> GetPrefsObjEnumerable(bool includeAssets)
=> GetObjPrefsList(includeAssets).SelectMany(op =>
op.holders.SelectMany(holder =>
holder.prefsSet.Select(prefs => (prefs, op.obj))));
private static bool IsInScene(GameObject go) => go.scene.name != null;
public static void UpdateObjPrefsIfNeed()
{
var time = (float) EditorApplication.timeSinceStartup;
needUpdate |= time - lastUpdateCheckTime > interval;
if (needUpdate)
{
if (DoUpdateGoPrefs())
{
onObjPrefsListChanged?.Invoke();
}
needUpdate = false;
lastUpdateCheckTime = time;
}
}
static bool DoUpdateGoPrefs()
{
using var gameObjectsScope = ListPool.Get(out var gameObjects);
gameObjects.AddRange(
Resources.FindObjectsOfTypeAll()
.Where(go => PrefabStageUtility.GetPrefabStage(go) == null) // ignore GameObject in PrefabStage
);
var prefabSources = new HashSet(gameObjects.Where(IsInScene).Select(PrefabUtility.GetCorrespondingObjectFromOriginalSource));
var objPrefsOfGameObjects = gameObjects
.Except(prefabSources) // ignore prefabs that the child is in the scene
.Select(go =>
{
var holders = go.GetComponents()
.Where(mono => mono != null)
.Where(mono => !Assembly.GetAssembly(mono.GetType()).GetName().Name.StartsWith("UnityEngine.")) // skip unity classes
.Select(mono => new PrefsHolderComponent() { component = mono, prefsSet = PrefsTypeUtility.SearchChildPrefsParams(mono) })
.Where(holder => holder.prefsSet.Any());
return (go, holders);
})
.Where(pair => pair.holders.Any(holder => holder.prefsSet.Any()))
.Select(pair => new ObjPrefs(
pair.go,
pair.holders
));
var objPrefsOfScriptableObjects = Resources.FindObjectsOfTypeAll(typeof(ScriptableObject))
.Where(so =>
{
var asmName = Assembly.GetAssembly(so.GetType()).GetName().Name;
return !asmName.StartsWith("Unity.") && !asmName.StartsWith("UnityEditor") && !asmName.StartsWith("UnityEngine");
})
.Select(so => (so, prefsList: PrefsTypeUtility.SearchChildPrefsParams(so)))
.Where(pair => pair.prefsList.Any())
.Select(pair => new ObjPrefs(
pair.so,
new[] { new PrefsHolderComponent() { component = pair.so, prefsSet = pair.prefsList } }
)
);
using var newListScope = ListPool.Get(out var newList);
newList.AddRange(objPrefsOfGameObjects
.Concat(objPrefsOfScriptableObjects)
.OrderBy(op => op.obj.name)
);
var change = !objPrefsList.SequenceEqual(newList, new ObjPrefsComparer());
if (change)
{
objPrefsList = new(newList);
}
return change;
}
}
}