com.alicizax.unity.framework/Editor/GameObjectPool/PoolConfigScriptableObjectEditor.cs
2026-03-26 10:49:41 +08:00

181 lines
7.1 KiB
C#

using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace AlicizaX
{
[CustomEditor(typeof(PoolConfigScriptableObject))]
public sealed class PoolConfigScriptableObjectEditor : UnityEditor.Editor
{
private const float VerticalSpacing = 4f;
private ReorderableList _configList;
private SerializedProperty _configsProperty;
private void OnEnable()
{
_configsProperty = serializedObject.FindProperty("configs");
_configList = new ReorderableList(serializedObject, _configsProperty, true, true, true, true)
{
drawHeaderCallback = rect => EditorGUI.LabelField(rect, "Pool Configs"),
drawElementCallback = DrawElement,
elementHeightCallback = GetElementHeight
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.HelpBox(
"每条配置定义一条匹配规则;真正的池按具体 assetPath 实例化,不再共享一个目录级总容量。",
MessageType.Info);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Normalize"))
{
serializedObject.ApplyModifiedProperties();
NormalizeAndSort(shouldSort: false);
serializedObject.Update();
}
if (GUILayout.Button("Normalize And Sort"))
{
serializedObject.ApplyModifiedProperties();
NormalizeAndSort(shouldSort: true);
serializedObject.Update();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
_configList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
DrawValidation();
}
private void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
{
SerializedProperty element = _configsProperty.GetArrayElementAtIndex(index);
rect.y += 2f;
float lineHeight = EditorGUIUtility.singleLineHeight;
float wideFieldWidth = rect.width * 0.6f;
float narrowFieldWidth = rect.width - wideFieldWidth - 6f;
SerializedProperty group = element.FindPropertyRelative("group");
SerializedProperty assetPath = element.FindPropertyRelative("assetPath");
SerializedProperty matchMode = element.FindPropertyRelative("matchMode");
SerializedProperty loaderType = element.FindPropertyRelative("resourceLoaderType");
SerializedProperty capacity = element.FindPropertyRelative("capacity");
SerializedProperty prewarmCount = element.FindPropertyRelative("prewarmCount");
SerializedProperty idleTimeout = element.FindPropertyRelative("instanceIdleTimeout");
SerializedProperty unloadDelay = element.FindPropertyRelative("prefabUnloadDelay");
SerializedProperty preloadOnInitialize = element.FindPropertyRelative("preloadOnInitialize");
Rect line1 = new Rect(rect.x, rect.y, rect.width, lineHeight);
Rect line2 = new Rect(rect.x, line1.yMax + VerticalSpacing, rect.width, lineHeight);
Rect line3Left = new Rect(rect.x, line2.yMax + VerticalSpacing, wideFieldWidth, lineHeight);
Rect line3Right = new Rect(line3Left.xMax + 6f, line3Left.y, narrowFieldWidth, lineHeight);
Rect line4Left = new Rect(rect.x, line3Left.yMax + VerticalSpacing, wideFieldWidth, lineHeight);
Rect line4Right = new Rect(line4Left.xMax + 6f, line4Left.y, narrowFieldWidth, lineHeight);
Rect line5Left = new Rect(rect.x, line4Left.yMax + VerticalSpacing, wideFieldWidth, lineHeight);
Rect line5Right = new Rect(line5Left.xMax + 6f, line5Left.y, narrowFieldWidth, lineHeight);
EditorGUI.PropertyField(line1, assetPath);
EditorGUI.PropertyField(line2, group);
EditorGUI.PropertyField(line3Left, matchMode);
EditorGUI.PropertyField(line3Right, loaderType);
EditorGUI.PropertyField(line4Left, capacity);
EditorGUI.PropertyField(line4Right, prewarmCount);
EditorGUI.PropertyField(line5Left, idleTimeout);
EditorGUI.PropertyField(line5Right, unloadDelay);
Rect line6 = new Rect(rect.x, line5Left.yMax + VerticalSpacing, rect.width, lineHeight);
EditorGUI.PropertyField(line6, preloadOnInitialize);
}
private float GetElementHeight(int index)
{
float lineHeight = EditorGUIUtility.singleLineHeight;
return lineHeight * 6f + VerticalSpacing * 7f;
}
private void NormalizeAndSort(bool shouldSort)
{
var asset = (PoolConfigScriptableObject)target;
asset.Normalize();
if (shouldSort)
{
asset.configs.Sort(PoolConfig.CompareByPriority);
}
EditorUtility.SetDirty(asset);
}
private void DrawValidation()
{
var asset = (PoolConfigScriptableObject)target;
if (asset.configs == null || asset.configs.Count == 0)
{
return;
}
List<string> warnings = BuildWarnings(asset.configs);
for (int i = 0; i < warnings.Count; i++)
{
EditorGUILayout.HelpBox(warnings[i], MessageType.Warning);
}
}
private static List<string> BuildWarnings(List<PoolConfig> configs)
{
var warnings = new List<string>();
for (int i = 0; i < configs.Count; i++)
{
PoolConfig config = configs[i];
if (config == null)
{
warnings.Add($"Element {i} is null.");
continue;
}
if (string.IsNullOrWhiteSpace(config.assetPath))
{
warnings.Add($"Element {i} has an empty asset path.");
}
if (config.matchMode == PoolMatchMode.Prefix && config.preloadOnInitialize)
{
warnings.Add($"Element {i} uses Prefix matching and preloadOnInitialize. Prefix rules cannot infer a concrete asset to prewarm.");
}
for (int j = i + 1; j < configs.Count; j++)
{
PoolConfig other = configs[j];
if (other == null)
{
continue;
}
bool duplicate =
string.Equals(config.group, other.group, System.StringComparison.Ordinal) &&
config.matchMode == other.matchMode &&
string.Equals(config.assetPath, other.assetPath, System.StringComparison.Ordinal);
if (duplicate)
{
warnings.Add($"Duplicate rule detected between elements {i} and {j}: {config.group}:{config.assetPath}.");
}
}
}
return warnings;
}
}
}