CORE dashboard + a lot of changes

This commit is contained in:
2026-06-22 20:09:15 +02:00
parent 19d6bd934a
commit 89fa0b23b2
101 changed files with 1525 additions and 177 deletions
@@ -0,0 +1,238 @@
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());
}
}
}