238 lines
9.0 KiB
C#
238 lines
9.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using RPGCore.Editor.CoreManager.Validators;
|
|
using RPGCoreCommon.Helpers;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
namespace RPGCore.Editor.CoreManager
|
|
{
|
|
internal static class ValidationSolver
|
|
{
|
|
private static readonly List<IValidator> Validators =
|
|
TypeCache.GetTypesDerivedFrom<IValidator>()
|
|
.Select(Activator.CreateInstance)
|
|
.Cast<IValidator>()
|
|
.ToList();
|
|
|
|
[InitializeOnLoadMethod]
|
|
private static void AutomaticValidation_Initialize() => ObjectChangeEvents.changesPublished += AutomaticValidation;
|
|
|
|
private static void AutomaticValidation(ref ObjectChangeEventStream stream)
|
|
{
|
|
var instanceIds = new List<int>();
|
|
|
|
for (var i = 0; i < stream.length; i++)
|
|
{
|
|
if (stream.GetEventType(i) != ObjectChangeKind.ChangeGameObjectStructure) continue;
|
|
|
|
stream.GetChangeGameObjectStructureEvent(0, out var ev);
|
|
instanceIds.Add(ev.instanceId);
|
|
}
|
|
|
|
if (instanceIds.Count == 0) return;
|
|
|
|
var validations = Check(instanceIds);
|
|
FixAll(validations);
|
|
|
|
// Can't finish because of exception or error
|
|
validations
|
|
.Where(v => v.result == false)
|
|
.Select(v => "--- ERROR: " + v.resultMessage +
|
|
"\n> " + v.contextPath +
|
|
"\n> " + v.hierarchyPath +
|
|
"\n> " + v.validator.GetType().Name +
|
|
"\n> " + v.message)
|
|
.ForEach(Debug.LogError);
|
|
|
|
// No fix defined
|
|
validations
|
|
.Where(v => v.result == null)
|
|
.Select(v => "--- WARNING: " + v.resultMessage +
|
|
"\n> " + v.contextPath +
|
|
"\n> " + v.hierarchyPath +
|
|
"\n> " + v.validator.GetType().Name +
|
|
"\n> " + v.message)
|
|
.ForEach(Debug.LogWarning);
|
|
}
|
|
|
|
internal static List<Validation> Check(List<int> instanceIds)
|
|
{
|
|
var validations = new List<Validation>();
|
|
|
|
foreach (var instanceId in instanceIds)
|
|
{
|
|
var obj = EditorUtility.EntityIdToObject(instanceId);
|
|
if (obj is not GameObject go) continue;
|
|
|
|
Check(go, validations, false);
|
|
}
|
|
|
|
return validations;
|
|
}
|
|
|
|
internal static List<Validation> Check(string[] paths)
|
|
{
|
|
var validations = new List<Validation>();
|
|
|
|
foreach (var guid in AssetDatabase.FindAssetGUIDs("t:Scene", paths))
|
|
{
|
|
var scene = EditorSceneManager.OpenPreviewScene(AssetDatabase.GUIDToAssetPath(guid));
|
|
foreach (var go in scene.GetRootGameObjects())
|
|
{
|
|
try
|
|
{
|
|
Check(go, validations, true);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
EditorSceneManager.ClosePreviewScene(scene);
|
|
}
|
|
|
|
foreach (var guid in AssetDatabase.FindAssetGUIDs("t:Prefab", paths))
|
|
{
|
|
var prefabPath = AssetDatabase.GUIDToAssetPath(guid);
|
|
var prefab = PrefabUtility.LoadPrefabContents(prefabPath);
|
|
|
|
try
|
|
{
|
|
Check(prefab, validations, true);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// ignored
|
|
}
|
|
|
|
PrefabUtility.UnloadPrefabContents(prefab);
|
|
}
|
|
|
|
return validations;
|
|
}
|
|
|
|
internal static void Check(GameObject go, List<Validation> result, bool recursive)
|
|
{
|
|
// On scene and part of other prefab - don't validate that, it will be validated separately anyway
|
|
if (PrefabUtility.IsPartOfPrefabInstance(go)) return;
|
|
|
|
foreach (var validator in Validators)
|
|
{
|
|
var message = validator.Check(go);
|
|
if (string.IsNullOrEmpty(message)) continue;
|
|
|
|
result.Add(new Validation
|
|
{
|
|
validator = validator,
|
|
message = message,
|
|
contextPath = GetContextPath(go),
|
|
hierarchyPath = GetHierarchyPath(go),
|
|
});
|
|
}
|
|
|
|
if (!recursive) return;
|
|
for (var i = 0; i < go.transform.childCount; i++)
|
|
Check(go.transform.GetChild(i).gameObject, result, true);
|
|
}
|
|
|
|
internal static void FixAll(List<Validation> validations)
|
|
{
|
|
foreach (var validation in validations)
|
|
{
|
|
var contextType = AssetDatabase.GetMainAssetTypeAtPath(validation.contextPath);
|
|
|
|
if (contextType == typeof(SceneAsset)) FixOnScene(validation);
|
|
else if (contextType == typeof(GameObject)) FixOnPrefab(validation);
|
|
else throw new Exception($"Given file '{validation.contextPath}' is not prefab nor scene!");
|
|
}
|
|
}
|
|
|
|
private static void FixOnScene(Validation validation)
|
|
{
|
|
var scene = Enumerable.Range(0, SceneManager.loadedSceneCount)
|
|
.Select(SceneManager.GetSceneAt)
|
|
.FirstOrDefault(scene => scene.path == validation.contextPath);
|
|
var isActive = scene.IsValid();
|
|
if (!isActive) scene = EditorSceneManager.OpenPreviewScene(validation.contextPath);
|
|
var go = FindGameObject(scene.GetRootGameObjects(), validation);
|
|
FixGameObject(go, validation);
|
|
if (isActive) EditorUtility.SetDirty(go);
|
|
if (!isActive) EditorSceneManager.SaveScene(scene);
|
|
if (!isActive) EditorSceneManager.ClosePreviewScene(scene);
|
|
}
|
|
|
|
private static void FixOnPrefab(Validation validation)
|
|
{
|
|
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
|
var isActive = prefabStage && prefabStage.assetPath == validation.contextPath;
|
|
var prefabRoot = isActive
|
|
? prefabStage.prefabContentsRoot
|
|
: PrefabUtility.LoadPrefabContents(validation.contextPath);
|
|
var go = FindGameObject(new[] { prefabRoot }, validation);
|
|
FixGameObject(go, validation);
|
|
if (isActive) EditorUtility.SetDirty(go);
|
|
if (!isActive) PrefabUtility.SaveAsPrefabAsset(prefabRoot, validation.contextPath);
|
|
if (!isActive) PrefabUtility.UnloadPrefabContents(prefabRoot);
|
|
}
|
|
|
|
private static GameObject FindGameObject(GameObject[] roots, Validation validation)
|
|
{
|
|
var hierarchyPathArray = validation.hierarchyPath.Split('/', 2);
|
|
var root = roots.FirstOrDefault(go => go.name == hierarchyPathArray[0]);
|
|
if (!root) throw new Exception($"Root '{hierarchyPathArray[0]}' not found in '{validation.contextPath}'!");
|
|
var go = hierarchyPathArray.Length > 1 ? root.transform.Find(hierarchyPathArray[1])?.gameObject : root;
|
|
if (!go) throw new Exception($"GameObject '{validation.hierarchyPath}' not found in '{validation.contextPath}'!");
|
|
return go;
|
|
}
|
|
|
|
private static void FixGameObject(GameObject go, Validation validation)
|
|
{
|
|
try
|
|
{
|
|
validation.validator.Fix(go);
|
|
validation.resultMessage = "Done.";
|
|
validation.result = true;
|
|
}
|
|
catch (NotImplementedException)
|
|
{
|
|
validation.resultMessage = "No Auto-fix defined. Do it yourself.";
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
validation.result = false;
|
|
validation.resultMessage = e.Message;
|
|
}
|
|
}
|
|
|
|
private static string GetContextPath(GameObject go)
|
|
{
|
|
// ON PREFAB STAGE - this one will be used only in realtime correcting, never in verify tab
|
|
if (PrefabStageUtility.GetPrefabStage(go) is {} stage) return stage.assetPath;
|
|
|
|
// ON SCENE
|
|
if (go.scene.IsValid()) return go.scene.path;
|
|
|
|
// PREFAB ASSET
|
|
if (PrefabUtility.IsPartOfPrefabAsset(go)) return go.scene.path;
|
|
|
|
throw new Exception("No valid context path found, sad pepe :(");
|
|
}
|
|
|
|
private static string GetHierarchyPath(GameObject go)
|
|
{
|
|
var path = new List<string>();
|
|
|
|
while (go is not null)
|
|
{
|
|
path.Add(go.name);
|
|
go = go.transform.parent?.gameObject;
|
|
}
|
|
|
|
return string.Join("/", path.AsEnumerable().Reverse());
|
|
}
|
|
}
|
|
} |