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 warnings = BuildWarnings(asset.configs); for (int i = 0; i < warnings.Count; i++) { EditorGUILayout.HelpBox(warnings[i], MessageType.Warning); } } private static List BuildWarnings(List configs) { var warnings = new List(); 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; } } }