[Opt]重构GameObjectPool
This commit is contained in:
parent
03f479dc0e
commit
49353c6d61
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
@ -7,49 +8,57 @@ namespace AlicizaX
|
||||
[CustomEditor(typeof(GameObjectPoolManager))]
|
||||
public sealed class GameObjectPoolEditor : UnityEditor.Editor
|
||||
{
|
||||
private readonly Dictionary<string, bool> _foldoutState = new Dictionary<string, bool>();
|
||||
private readonly Dictionary<string, bool> _foldoutState = new Dictionary<string, bool>(StringComparer.Ordinal);
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
DrawDefaultInspector();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
var pool = (GameObjectPoolManager)target;
|
||||
var poolManager = (GameObjectPoolManager)target;
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Enter Play Mode to inspect runtime pool state.", MessageType.Info);
|
||||
EditorGUILayout.HelpBox("Enter Play Mode to inspect the runtime pool.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pool.showDetailedInfo)
|
||||
if (!poolManager.showDetailedInfo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
DrawRuntimeState(pool);
|
||||
DrawRuntimeState(poolManager);
|
||||
}
|
||||
|
||||
public override bool RequiresConstantRepaint()
|
||||
{
|
||||
var pool = target as GameObjectPoolManager;
|
||||
return pool != null && Application.isPlaying && pool.showDetailedInfo;
|
||||
var poolManager = target as GameObjectPoolManager;
|
||||
return poolManager != null && Application.isPlaying && poolManager.showDetailedInfo;
|
||||
}
|
||||
|
||||
private void DrawRuntimeState(GameObjectPoolManager poolManager)
|
||||
{
|
||||
if (!poolManager.IsReady)
|
||||
GameObjectPoolSummarySnapshot summary = poolManager.GetDebugSummary();
|
||||
DrawSummary(summary);
|
||||
|
||||
if (summary.WaitingForBootstrap)
|
||||
{
|
||||
EditorGUILayout.HelpBox("GameObjectPool is initializing.", MessageType.Info);
|
||||
EditorGUILayout.HelpBox("Waiting for YooAssets bootstrap before pool catalog build.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!summary.IsReady)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Pool manager is not ready.", MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
List<GameObjectPoolSnapshot> snapshots = poolManager.GetDebugSnapshots();
|
||||
if (snapshots.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("No runtime pools have been created yet.", MessageType.Info);
|
||||
EditorGUILayout.HelpBox("No pooled runtime instances exist yet.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -59,6 +68,22 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawSummary(in GameObjectPoolSummarySnapshot summary)
|
||||
{
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
EditorGUILayout.LabelField("Runtime Summary", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("Ready", summary.IsReady ? "Yes" : "No");
|
||||
EditorGUILayout.LabelField("Waiting Bootstrap", summary.WaitingForBootstrap ? "Yes" : "No");
|
||||
EditorGUILayout.LabelField("Pools", summary.PoolCount.ToString());
|
||||
EditorGUILayout.LabelField("Loaded Prefabs", summary.LoadedPrefabCount.ToString());
|
||||
EditorGUILayout.LabelField("Instances", summary.TotalInstanceCount.ToString());
|
||||
EditorGUILayout.LabelField("Active", summary.ActiveInstanceCount.ToString());
|
||||
EditorGUILayout.LabelField("Inactive", summary.InactiveInstanceCount.ToString());
|
||||
EditorGUILayout.LabelField("Pending Maintenance", summary.PendingMaintenanceCount.ToString());
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
private void DrawSnapshot(GameObjectPoolSnapshot snapshot)
|
||||
{
|
||||
if (snapshot == null)
|
||||
@ -66,40 +91,49 @@ namespace AlicizaX
|
||||
return;
|
||||
}
|
||||
|
||||
string key = $"{snapshot.group}|{snapshot.assetPath}";
|
||||
if (!_foldoutState.ContainsKey(key))
|
||||
string entryLabel = string.IsNullOrWhiteSpace(snapshot.entryName) ? snapshot.assetPath : snapshot.entryName;
|
||||
string foldoutKey = string.Concat(entryLabel, "|", snapshot.assetPath);
|
||||
if (!_foldoutState.ContainsKey(foldoutKey))
|
||||
{
|
||||
_foldoutState[key] = false;
|
||||
_foldoutState[foldoutKey] = false;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
_foldoutState[key] = EditorGUILayout.Foldout(
|
||||
_foldoutState[key],
|
||||
$"{snapshot.group} | {snapshot.assetPath} ({snapshot.activeCount}/{snapshot.totalCount})",
|
||||
true);
|
||||
string foldoutLabel = string.Format(
|
||||
"{0} [{1}/{2}] Hit:{3}/{4}",
|
||||
entryLabel,
|
||||
snapshot.activeCount,
|
||||
snapshot.totalCount,
|
||||
snapshot.hitCount,
|
||||
snapshot.acquireCount);
|
||||
|
||||
if (_foldoutState[key])
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
_foldoutState[foldoutKey] = EditorGUILayout.Foldout(_foldoutState[foldoutKey], foldoutLabel, true);
|
||||
if (_foldoutState[foldoutKey])
|
||||
{
|
||||
EditorGUILayout.LabelField("Entry", snapshot.entryName);
|
||||
EditorGUILayout.LabelField("Rule", entryLabel);
|
||||
EditorGUILayout.LabelField("Asset Path", snapshot.assetPath);
|
||||
EditorGUILayout.LabelField("Loader", snapshot.loaderType.ToString());
|
||||
EditorGUILayout.LabelField("Overflow Policy", snapshot.overflowPolicy.ToString());
|
||||
EditorGUILayout.LabelField("Min Retained", snapshot.minRetained.ToString());
|
||||
EditorGUILayout.LabelField("Retain Target", snapshot.retainTarget.ToString());
|
||||
EditorGUILayout.LabelField("Soft Capacity", snapshot.softCapacity.ToString());
|
||||
EditorGUILayout.LabelField("Hard Capacity", snapshot.hardCapacity.ToString());
|
||||
EditorGUILayout.LabelField("Capacity", snapshot.hardCapacity.ToString());
|
||||
EditorGUILayout.LabelField("Runtime Capacity", snapshot.runtimeHardCapacity.ToString());
|
||||
EditorGUILayout.LabelField("Inactive", snapshot.inactiveCount.ToString());
|
||||
EditorGUILayout.LabelField("Prefab Loaded", snapshot.prefabLoaded ? "Yes" : "No");
|
||||
EditorGUILayout.LabelField("Prefab Idle", $"{snapshot.prefabIdleDuration:F1}s");
|
||||
EditorGUILayout.LabelField("Prefab Cold", FormatSeconds(snapshot.prefabIdleDuration));
|
||||
EditorGUILayout.LabelField("Wake Count", snapshot.prefabWakeCount.ToString());
|
||||
EditorGUILayout.LabelField("Wake Gap", snapshot.prefabWakeGap < 0f ? "N/A" : FormatSeconds(snapshot.prefabWakeGap));
|
||||
EditorGUILayout.LabelField("Unload Delay", snapshot.prefabUnloadDelay < 0f ? "N/A" : FormatSeconds(snapshot.prefabUnloadDelay));
|
||||
EditorGUILayout.LabelField("Next Maintenance", snapshot.nextMaintenanceIn < 0f ? "None" : FormatSeconds(snapshot.nextMaintenanceIn));
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Acquire", snapshot.acquireCount.ToString());
|
||||
EditorGUILayout.LabelField("Release", snapshot.releaseCount.ToString());
|
||||
EditorGUILayout.LabelField("Hit", snapshot.hitCount.ToString());
|
||||
EditorGUILayout.LabelField("Miss", snapshot.missCount.ToString());
|
||||
EditorGUILayout.LabelField("Expand", snapshot.expandCount.ToString());
|
||||
EditorGUILayout.LabelField("Exhausted", snapshot.exhaustedCount.ToString());
|
||||
EditorGUILayout.LabelField("Auto Recycle", snapshot.autoRecycleCount.ToString());
|
||||
EditorGUILayout.LabelField("Destroy", snapshot.destroyCount.ToString());
|
||||
EditorGUILayout.LabelField("Peak Active", snapshot.peakActive.ToString());
|
||||
EditorGUILayout.LabelField("Peak Total", snapshot.peakTotal.ToString());
|
||||
EditorGUILayout.LabelField("Peak Active Short", snapshot.peakActiveShort.ToString());
|
||||
EditorGUILayout.LabelField("Peak Active Long", snapshot.peakActiveLong.ToString());
|
||||
|
||||
if (snapshot.instances.Count > 0)
|
||||
{
|
||||
@ -116,25 +150,31 @@ namespace AlicizaX
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
private static void DrawInstance(GameObjectPoolInstanceSnapshot instance)
|
||||
private static void DrawInstance(GameObjectPoolInstanceSnapshot snapshot)
|
||||
{
|
||||
if (instance == null)
|
||||
if (snapshot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal("box");
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField(instance.instanceName, EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("State", instance.isActive ? "Active" : "Inactive");
|
||||
EditorGUILayout.LabelField("Life", $"{instance.lifeDuration:F1}s");
|
||||
if (!instance.isActive)
|
||||
EditorGUILayout.LabelField(snapshot.instanceName, EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("State", snapshot.isActive ? "Active" : "Inactive");
|
||||
EditorGUILayout.LabelField("Life", FormatSeconds(snapshot.lifeDuration));
|
||||
if (!snapshot.isActive)
|
||||
{
|
||||
EditorGUILayout.LabelField("Idle", $"{instance.idleDuration:F1}s");
|
||||
EditorGUILayout.LabelField("Idle", FormatSeconds(snapshot.idleDuration));
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.ObjectField(instance.gameObject, typeof(GameObject), true, GUILayout.Width(120));
|
||||
EditorGUILayout.ObjectField(snapshot.gameObject, typeof(GameObject), true, GUILayout.Width(140f));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private static string FormatSeconds(float seconds)
|
||||
{
|
||||
return seconds.ToString("F2") + "s";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ using AlicizaX.Editor;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
@ -12,37 +13,47 @@ namespace AlicizaX
|
||||
{
|
||||
private const float MinLeftWidth = 240f;
|
||||
private const float InitialLeftWidth = 300f;
|
||||
private const int ListItemHeight = 46;
|
||||
private const string WindowTitle = "Pool Config Editor";
|
||||
private const int ListItemHeight = 24;
|
||||
private const string WindowTitle = "对象池配置编辑器";
|
||||
|
||||
private static readonly Color LeftPanelColor = new Color(0.22f, 0.22f, 0.22f, 1f);
|
||||
private static readonly Color RightPanelColor = new Color(0.16f, 0.16f, 0.16f, 1f);
|
||||
private static readonly Color DescriptionColor = new Color(0.72f, 0.72f, 0.72f, 1f);
|
||||
private static readonly List<string> MatchModeOptions = new List<string> { "精确匹配", "前缀匹配" };
|
||||
private static readonly List<string> LoaderTypeOptions = new List<string> { "AssetBundle", "Resources" };
|
||||
|
||||
[SerializeField]
|
||||
private PoolConfigScriptableObject _asset;
|
||||
[SerializeField]
|
||||
private string _assetGuid;
|
||||
private SerializedObject _serializedObject;
|
||||
private SerializedProperty _entriesProperty;
|
||||
private readonly List<int> _entryIndices = new List<int>();
|
||||
|
||||
[SerializeField]
|
||||
private int _selectedIndex;
|
||||
[SerializeField]
|
||||
private bool _hasUnsavedChanges;
|
||||
[SerializeField]
|
||||
private Vector2 _entryListScrollPosition;
|
||||
|
||||
private ToolbarButton _saveButton;
|
||||
private Label _titleLabel;
|
||||
private VisualElement _leftPane;
|
||||
private ListView _listView;
|
||||
private IMGUIContainer _entryListContainer;
|
||||
private ReorderableList _entryList;
|
||||
private ScrollView _detailScrollView;
|
||||
private Label _detailTitleLabel;
|
||||
private VisualElement _detailFieldsContainer;
|
||||
private VisualElement _emptyContainer;
|
||||
|
||||
[MenuItem("AlicizaX/GameObjectPool/Open PoolConfig Editor")]
|
||||
public static void OpenWindow()
|
||||
private static void OpenForAsset(PoolConfigScriptableObject asset)
|
||||
{
|
||||
Open(Selection.activeObject as PoolConfigScriptableObject);
|
||||
}
|
||||
if (asset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public static void Open(PoolConfigScriptableObject asset)
|
||||
{
|
||||
PoolConfigEditorWindow window = GetWindow<PoolConfigEditorWindow>(false, WindowTitle, true);
|
||||
window.minSize = new Vector2(920f, 560f);
|
||||
window.SetAsset(asset);
|
||||
@ -58,7 +69,7 @@ namespace AlicizaX
|
||||
return false;
|
||||
}
|
||||
|
||||
Open(asset);
|
||||
OpenForAsset(asset);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -66,15 +77,8 @@ namespace AlicizaX
|
||||
{
|
||||
titleContent = new GUIContent(WindowTitle, EditorGUIUtility.IconContent("ScriptableObject Icon").image);
|
||||
BuildUi();
|
||||
|
||||
if (_asset == null && Selection.activeObject is PoolConfigScriptableObject selectedAsset)
|
||||
{
|
||||
SetAsset(selectedAsset);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshUi();
|
||||
}
|
||||
RestoreWindowState();
|
||||
RefreshUi();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
@ -85,22 +89,8 @@ namespace AlicizaX
|
||||
BuildUi();
|
||||
}
|
||||
|
||||
if (_asset == null && Selection.activeObject is PoolConfigScriptableObject selectedAsset)
|
||||
{
|
||||
SetAsset(selectedAsset);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshUi();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChange()
|
||||
{
|
||||
if (Selection.activeObject is PoolConfigScriptableObject selectedAsset && selectedAsset != _asset)
|
||||
{
|
||||
SetAsset(selectedAsset);
|
||||
}
|
||||
RestoreWindowState();
|
||||
RefreshUi();
|
||||
}
|
||||
|
||||
private void BuildUi()
|
||||
@ -113,7 +103,7 @@ namespace AlicizaX
|
||||
|
||||
_saveButton = new ToolbarButton(SaveAsset)
|
||||
{
|
||||
tooltip = "Save PoolConfig"
|
||||
tooltip = "保存当前 PoolConfig 配置"
|
||||
};
|
||||
_saveButton.Add(new Image
|
||||
{
|
||||
@ -161,63 +151,10 @@ namespace AlicizaX
|
||||
|
||||
private void BuildLeftPane()
|
||||
{
|
||||
_listView = new ListView
|
||||
{
|
||||
selectionType = SelectionType.Single,
|
||||
virtualizationMethod = CollectionVirtualizationMethod.FixedHeight,
|
||||
reorderable = false,
|
||||
showBorder = false,
|
||||
showAlternatingRowBackgrounds = AlternatingRowBackground.None,
|
||||
style =
|
||||
{
|
||||
flexGrow = 1f,
|
||||
marginBottom = 4f
|
||||
},
|
||||
fixedItemHeight = ListItemHeight
|
||||
};
|
||||
|
||||
_listView.makeItem = MakeListItem;
|
||||
_listView.bindItem = BindListItem;
|
||||
_listView.selectionChanged += _ => OnListSelectionChanged();
|
||||
_leftPane.Add(_listView);
|
||||
|
||||
VisualElement footer = new VisualElement();
|
||||
footer.style.flexDirection = FlexDirection.Row;
|
||||
footer.style.justifyContent = Justify.Center;
|
||||
footer.style.alignItems = Align.Center;
|
||||
footer.style.flexShrink = 0f;
|
||||
footer.style.height = 28f;
|
||||
|
||||
Button addButton = new Button(AddEntry)
|
||||
{
|
||||
tooltip = "Add Item"
|
||||
};
|
||||
addButton.style.width = 24f;
|
||||
addButton.style.height = 20f;
|
||||
addButton.style.marginRight = 4f;
|
||||
addButton.Add(new Image
|
||||
{
|
||||
image = EditorUtils.Styles.PlusIcon.image,
|
||||
scaleMode = ScaleMode.ScaleToFit
|
||||
});
|
||||
|
||||
Button removeButton = new Button(RemoveEntry)
|
||||
{
|
||||
tooltip = "Remove Item",
|
||||
name = "remove-button"
|
||||
};
|
||||
removeButton.style.width = 24f;
|
||||
removeButton.style.height = 20f;
|
||||
removeButton.Add(new Image
|
||||
{
|
||||
image = EditorUtils.Styles.MinusIcon.image,
|
||||
scaleMode = ScaleMode.ScaleToFit
|
||||
});
|
||||
removeButton.SetEnabled(false);
|
||||
|
||||
footer.Add(addButton);
|
||||
footer.Add(removeButton);
|
||||
_leftPane.Add(footer);
|
||||
_entryListContainer = new IMGUIContainer(DrawEntryList);
|
||||
_entryListContainer.style.flexGrow = 1f;
|
||||
_entryListContainer.style.marginBottom = 4f;
|
||||
_leftPane.Add(_entryListContainer);
|
||||
}
|
||||
|
||||
private void BuildRightPane(VisualElement rightPane)
|
||||
@ -230,7 +167,7 @@ namespace AlicizaX
|
||||
justifyContent = Justify.Center
|
||||
}
|
||||
};
|
||||
_emptyContainer.Add(new HelpBox("No entry selected.", HelpBoxMessageType.Info));
|
||||
_emptyContainer.Add(new HelpBox("当前没有选中任何规则。", HelpBoxMessageType.Info));
|
||||
|
||||
_detailScrollView = new ScrollView
|
||||
{
|
||||
@ -256,59 +193,53 @@ namespace AlicizaX
|
||||
rightPane.Add(_detailScrollView);
|
||||
}
|
||||
|
||||
private VisualElement MakeListItem()
|
||||
{
|
||||
VisualElement root = new VisualElement();
|
||||
root.style.height = ListItemHeight;
|
||||
root.style.flexDirection = FlexDirection.Column;
|
||||
root.style.justifyContent = Justify.Center;
|
||||
root.style.paddingLeft = 8f;
|
||||
root.style.paddingRight = 8f;
|
||||
root.style.paddingTop = 6f;
|
||||
root.style.paddingBottom = 6f;
|
||||
root.style.marginBottom = 2f;
|
||||
|
||||
Label primary = new Label { name = "primary" };
|
||||
primary.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||
|
||||
Label secondary = new Label { name = "secondary" };
|
||||
secondary.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||
secondary.style.fontSize = 11f;
|
||||
secondary.style.color = new Color(0.72f, 0.72f, 0.72f, 1f);
|
||||
|
||||
root.Add(primary);
|
||||
root.Add(secondary);
|
||||
return root;
|
||||
}
|
||||
|
||||
private void BindListItem(VisualElement element, int index)
|
||||
{
|
||||
SerializedProperty entry = GetEntryAt(index);
|
||||
element.Q<Label>("primary").text = GetPrimaryLabel(entry);
|
||||
element.Q<Label>("secondary").text = GetSecondaryLabel(entry);
|
||||
}
|
||||
|
||||
private void SetAsset(PoolConfigScriptableObject asset)
|
||||
{
|
||||
_asset = asset;
|
||||
_assetGuid = GetAssetGuid(asset);
|
||||
_selectedIndex = 0;
|
||||
_hasUnsavedChanges = false;
|
||||
_entryListScrollPosition = Vector2.zero;
|
||||
RebindAssetState();
|
||||
RefreshUi();
|
||||
}
|
||||
|
||||
private void RestoreWindowState()
|
||||
{
|
||||
if (_asset == null && !string.IsNullOrEmpty(_assetGuid))
|
||||
{
|
||||
string assetPath = AssetDatabase.GUIDToAssetPath(_assetGuid);
|
||||
if (!string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
_asset = AssetDatabase.LoadAssetAtPath<PoolConfigScriptableObject>(assetPath);
|
||||
}
|
||||
}
|
||||
else if (_asset != null)
|
||||
{
|
||||
_assetGuid = GetAssetGuid(_asset);
|
||||
}
|
||||
|
||||
RebindAssetState();
|
||||
}
|
||||
|
||||
private void RebindAssetState()
|
||||
{
|
||||
_entryList = null;
|
||||
|
||||
if (_asset == null)
|
||||
{
|
||||
_assetGuid = string.Empty;
|
||||
_serializedObject = null;
|
||||
_entriesProperty = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_asset.Normalize();
|
||||
_serializedObject = new SerializedObject(_asset);
|
||||
_entriesProperty = _serializedObject.FindProperty("entries");
|
||||
RebuildEntryIndices();
|
||||
ClampSelection();
|
||||
_entryIndices.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshUi();
|
||||
_asset.Normalize();
|
||||
_serializedObject = new SerializedObject(_asset);
|
||||
_entriesProperty = _serializedObject.FindProperty("entries");
|
||||
RebuildEntryIndices();
|
||||
ClampSelection();
|
||||
}
|
||||
|
||||
private void RefreshUi()
|
||||
@ -324,24 +255,23 @@ namespace AlicizaX
|
||||
if (_asset == null || _serializedObject == null)
|
||||
{
|
||||
_entryIndices.Clear();
|
||||
_listView.itemsSource = _entryIndices;
|
||||
_listView.Rebuild();
|
||||
_entryList = null;
|
||||
_entryListContainer?.MarkDirtyRepaint();
|
||||
_emptyContainer.Clear();
|
||||
_emptyContainer.Add(new HelpBox("Select or double-click a PoolConfig asset to edit it in this window.", HelpBoxMessageType.Info));
|
||||
_emptyContainer.Add(new HelpBox("请选择或双击 PoolConfig 资源,然后在这个窗口里编辑对象池规则。", HelpBoxMessageType.Info));
|
||||
_emptyContainer.style.display = DisplayStyle.Flex;
|
||||
_detailScrollView.style.display = DisplayStyle.None;
|
||||
UpdateRemoveButtonState();
|
||||
return;
|
||||
}
|
||||
|
||||
RebuildEntryIndices();
|
||||
ClampSelection();
|
||||
EnsureEntryReorderableList();
|
||||
SyncEntryListSelection();
|
||||
_entryListContainer?.MarkDirtyRepaint();
|
||||
|
||||
_listView.itemsSource = _entryIndices;
|
||||
_listView.Rebuild();
|
||||
if (_entryIndices.Count > 0)
|
||||
{
|
||||
_listView.SetSelectionWithoutNotify(new[] { _selectedIndex });
|
||||
_emptyContainer.style.display = DisplayStyle.None;
|
||||
_detailScrollView.style.display = DisplayStyle.Flex;
|
||||
RebuildDetailFields();
|
||||
@ -349,12 +279,10 @@ namespace AlicizaX
|
||||
else
|
||||
{
|
||||
_emptyContainer.Clear();
|
||||
_emptyContainer.Add(new HelpBox("No entry selected.", HelpBoxMessageType.Info));
|
||||
_emptyContainer.Add(new HelpBox("当前没有可编辑的规则,请先新增一条对象池规则。", HelpBoxMessageType.Info));
|
||||
_emptyContainer.style.display = DisplayStyle.Flex;
|
||||
_detailScrollView.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
UpdateRemoveButtonState();
|
||||
}
|
||||
|
||||
private void RebuildEntryIndices()
|
||||
@ -367,24 +295,121 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRemoveButtonState()
|
||||
private void DrawEntryList()
|
||||
{
|
||||
Button removeButton = _leftPane?.Q<Button>("remove-button");
|
||||
removeButton?.SetEnabled(_entryIndices.Count > 0);
|
||||
}
|
||||
|
||||
private void OnListSelectionChanged()
|
||||
{
|
||||
if (_listView.selectedIndex < 0 || _listView.selectedIndex >= _entryIndices.Count)
|
||||
if (_asset == null || _serializedObject == null || _entriesProperty == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedIndex = _listView.selectedIndex;
|
||||
UpdateRemoveButtonState();
|
||||
EnsureEntryReorderableList();
|
||||
SyncEntryListSelection();
|
||||
|
||||
_serializedObject.Update();
|
||||
_entryListScrollPosition = EditorGUILayout.BeginScrollView(_entryListScrollPosition, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
|
||||
Rect listRect = GUILayoutUtility.GetRect(0f, _entryList.GetHeight(), GUILayout.ExpandWidth(true));
|
||||
_entryList.DoList(listRect);
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void EnsureEntryReorderableList()
|
||||
{
|
||||
if (_serializedObject == null || _entriesProperty == null)
|
||||
{
|
||||
_entryList = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_entryList != null && _entryList.serializedProperty == _entriesProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_entryList = new ReorderableList(_serializedObject, _entriesProperty, true, true, true, true)
|
||||
{
|
||||
elementHeight = ListItemHeight + 6f
|
||||
};
|
||||
|
||||
_entryList.drawHeaderCallback = rect =>
|
||||
{
|
||||
EditorGUI.LabelField(rect, Utility.Text.Format("对象池规则 ({0})", _entriesProperty.arraySize), EditorStyles.boldLabel);
|
||||
};
|
||||
_entryList.drawElementCallback = DrawEntryListElement;
|
||||
_entryList.onSelectCallback = OnEntryListSelected;
|
||||
_entryList.onAddCallback = OnEntryListAdd;
|
||||
_entryList.onRemoveCallback = OnEntryListRemove;
|
||||
_entryList.onReorderCallback = OnEntryListReordered;
|
||||
}
|
||||
|
||||
private void DrawEntryListElement(Rect rect, int index, bool isActive, bool isFocused)
|
||||
{
|
||||
SerializedProperty entry = GetEntryAt(index);
|
||||
if (entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
rect.y += 5f;
|
||||
|
||||
string primaryLabel = GetPrimaryLabel(entry);
|
||||
string assetPath = entry.FindPropertyRelative("assetPath").stringValue;
|
||||
string tooltip = string.IsNullOrWhiteSpace(assetPath) || string.Equals(primaryLabel, assetPath, System.StringComparison.Ordinal)
|
||||
? primaryLabel
|
||||
: assetPath;
|
||||
|
||||
Rect primaryRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
|
||||
EditorGUI.LabelField(primaryRect, new GUIContent(primaryLabel, tooltip), EditorStyles.boldLabel);
|
||||
}
|
||||
|
||||
private void OnEntryListSelected(ReorderableList list)
|
||||
{
|
||||
if (list.index < 0 || list.index >= _entryIndices.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedIndex = list.index;
|
||||
RebuildDetailFields();
|
||||
}
|
||||
|
||||
private void OnEntryListAdd(ReorderableList list)
|
||||
{
|
||||
AddEntry();
|
||||
}
|
||||
|
||||
private void OnEntryListRemove(ReorderableList list)
|
||||
{
|
||||
RemoveEntry();
|
||||
}
|
||||
|
||||
private void OnEntryListReordered(ReorderableList list)
|
||||
{
|
||||
if (_asset == null || _serializedObject == null || _entriesProperty == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshEntryPriorities();
|
||||
_serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
_asset.Normalize();
|
||||
RebuildEntryIndices();
|
||||
_selectedIndex = Mathf.Clamp(list.index, 0, _entryIndices.Count - 1);
|
||||
_hasUnsavedChanges = true;
|
||||
RefreshTitle();
|
||||
RebuildDetailFields();
|
||||
_entryListContainer?.MarkDirtyRepaint();
|
||||
}
|
||||
|
||||
private void SyncEntryListSelection()
|
||||
{
|
||||
if (_entryList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_entryList.index = _entryIndices.Count == 0 ? -1 : _selectedIndex;
|
||||
}
|
||||
|
||||
private void RebuildDetailFields()
|
||||
{
|
||||
if (_asset == null || _serializedObject == null)
|
||||
@ -409,16 +434,124 @@ namespace AlicizaX
|
||||
bool enterChildren = true;
|
||||
while (iterator.NextVisible(enterChildren) && !SerializedProperty.EqualContents(iterator, end))
|
||||
{
|
||||
PropertyField field = new PropertyField(iterator.Copy());
|
||||
field.RegisterCallback<SerializedPropertyChangeEvent>(OnDetailPropertyChanged);
|
||||
_detailFieldsContainer.Add(field);
|
||||
SerializedProperty currentProperty = iterator.Copy();
|
||||
if (!ShouldDisplayField(currentProperty.name))
|
||||
{
|
||||
enterChildren = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
_detailFieldsContainer.Add(CreateDetailField(currentProperty));
|
||||
enterChildren = false;
|
||||
}
|
||||
|
||||
_detailFieldsContainer.Bind(_serializedObject);
|
||||
}
|
||||
|
||||
private VisualElement CreateDetailField(SerializedProperty property)
|
||||
{
|
||||
if (TryCreateLocalizedEnumField(property, out VisualElement enumFieldContainer))
|
||||
{
|
||||
return enumFieldContainer;
|
||||
}
|
||||
|
||||
VisualElement container = new VisualElement();
|
||||
container.style.flexDirection = FlexDirection.Column;
|
||||
container.style.marginBottom = 4f;
|
||||
|
||||
string label = property.name == "group" ? "\u5206\u7ec4" : GetFieldLabel(property.name);
|
||||
PropertyField field = new PropertyField(property, label);
|
||||
if (IsReadOnlyField(property.name))
|
||||
{
|
||||
field.SetEnabled(false);
|
||||
}
|
||||
|
||||
field.RegisterCallback<SerializedPropertyChangeEvent>(OnDetailPropertyChanged);
|
||||
container.Add(field);
|
||||
|
||||
string description = property.name == "group"
|
||||
? "\u7528\u4e8e GameObjectPoolManager \u4e0b\u7684\u7a7a\u95f2\u8282\u70b9\u5f52\u7c7b\uff0c\u4e0d\u586b\u6216\u7a7a\u503c\u4f1a\u81ea\u52a8\u56de\u843d\u5230 DefaultGroup\u3002"
|
||||
: GetFieldDescription(property.name);
|
||||
if (!string.IsNullOrWhiteSpace(description))
|
||||
{
|
||||
Label descriptionLabel = new Label(description);
|
||||
descriptionLabel.style.whiteSpace = WhiteSpace.Normal;
|
||||
descriptionLabel.style.fontSize = 11f;
|
||||
descriptionLabel.style.color = DescriptionColor;
|
||||
descriptionLabel.style.marginLeft = 4f;
|
||||
descriptionLabel.style.marginRight = 4f;
|
||||
descriptionLabel.style.marginTop = -2f;
|
||||
descriptionLabel.style.marginBottom = 6f;
|
||||
container.Add(descriptionLabel);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private bool TryCreateLocalizedEnumField(SerializedProperty property, out VisualElement container)
|
||||
{
|
||||
List<string> options = GetEnumOptions(property.name);
|
||||
if (options == null)
|
||||
{
|
||||
container = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
container = new VisualElement();
|
||||
container.style.flexDirection = FlexDirection.Column;
|
||||
container.style.marginBottom = 4f;
|
||||
|
||||
int currentIndex = Mathf.Clamp(property.enumValueIndex, 0, options.Count - 1);
|
||||
PopupField<string> popupField = new PopupField<string>(GetFieldLabel(property.name), options, currentIndex);
|
||||
string propertyPath = property.propertyPath;
|
||||
popupField.RegisterValueChangedCallback(_ =>
|
||||
{
|
||||
if (_serializedObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int selectedIndex = options.IndexOf(popupField.value);
|
||||
if (selectedIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_serializedObject.Update();
|
||||
SerializedProperty targetProperty = _serializedObject.FindProperty(propertyPath);
|
||||
if (targetProperty == null || targetProperty.enumValueIndex == selectedIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
targetProperty.enumValueIndex = selectedIndex;
|
||||
ApplyDetailPropertyChanges();
|
||||
});
|
||||
container.Add(popupField);
|
||||
|
||||
string description = GetFieldDescription(property.name);
|
||||
if (!string.IsNullOrWhiteSpace(description))
|
||||
{
|
||||
Label descriptionLabel = new Label(description);
|
||||
descriptionLabel.style.whiteSpace = WhiteSpace.Normal;
|
||||
descriptionLabel.style.fontSize = 11f;
|
||||
descriptionLabel.style.color = DescriptionColor;
|
||||
descriptionLabel.style.marginLeft = 4f;
|
||||
descriptionLabel.style.marginRight = 4f;
|
||||
descriptionLabel.style.marginTop = -2f;
|
||||
descriptionLabel.style.marginBottom = 6f;
|
||||
container.Add(descriptionLabel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnDetailPropertyChanged(SerializedPropertyChangeEvent evt)
|
||||
{
|
||||
ApplyDetailPropertyChanges();
|
||||
}
|
||||
|
||||
private void ApplyDetailPropertyChanges()
|
||||
{
|
||||
if (_asset == null || _serializedObject == null)
|
||||
{
|
||||
@ -428,7 +561,7 @@ namespace AlicizaX
|
||||
_serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
_asset.Normalize();
|
||||
_hasUnsavedChanges = true;
|
||||
_listView?.RefreshItems();
|
||||
_entryListContainer?.MarkDirtyRepaint();
|
||||
RefreshTitle();
|
||||
_detailTitleLabel.text = GetPrimaryLabel(GetSelectedProperty());
|
||||
}
|
||||
@ -446,6 +579,7 @@ namespace AlicizaX
|
||||
_entriesProperty.InsertArrayElementAtIndex(index);
|
||||
SerializedProperty property = _entriesProperty.GetArrayElementAtIndex(index);
|
||||
InitializeNewEntry(property, index);
|
||||
RefreshEntryPriorities();
|
||||
|
||||
_serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
_asset.Normalize();
|
||||
@ -453,7 +587,6 @@ namespace AlicizaX
|
||||
_selectedIndex = index;
|
||||
_hasUnsavedChanges = true;
|
||||
RefreshUi();
|
||||
_listView.SetSelectionWithoutNotify(new[] { _selectedIndex });
|
||||
}
|
||||
|
||||
private void RemoveEntry()
|
||||
@ -465,6 +598,7 @@ namespace AlicizaX
|
||||
|
||||
_serializedObject.Update();
|
||||
_entriesProperty.DeleteArrayElementAtIndex(_selectedIndex);
|
||||
RefreshEntryPriorities();
|
||||
_serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
_asset.Normalize();
|
||||
RebuildEntryIndices();
|
||||
@ -476,38 +610,47 @@ namespace AlicizaX
|
||||
|
||||
_hasUnsavedChanges = true;
|
||||
RefreshUi();
|
||||
if (_entryIndices.Count > 0)
|
||||
{
|
||||
_listView.SetSelectionWithoutNotify(new[] { _selectedIndex });
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeNewEntry(SerializedProperty property, int index)
|
||||
{
|
||||
property.FindPropertyRelative("entryName").stringValue = $"PoolEntry{index + 1}";
|
||||
property.FindPropertyRelative("entryName").stringValue = Utility.Text.Format("对象池规则{0}", index + 1);
|
||||
property.FindPropertyRelative("group").stringValue = PoolEntry.DefaultGroup;
|
||||
property.FindPropertyRelative("assetPath").stringValue = string.Empty;
|
||||
property.FindPropertyRelative("matchMode").enumValueIndex = (int)PoolMatchMode.Exact;
|
||||
property.FindPropertyRelative("loaderType").enumValueIndex = (int)PoolResourceLoaderType.AssetBundle;
|
||||
property.FindPropertyRelative("overflowPolicy").enumValueIndex = (int)PoolOverflowPolicy.FailFast;
|
||||
property.FindPropertyRelative("trimPolicy").enumValueIndex = (int)PoolTrimPolicy.IdleOnly;
|
||||
property.FindPropertyRelative("activationMode").enumValueIndex = (int)PoolActivationMode.SetActive;
|
||||
property.FindPropertyRelative("resetMode").enumValueIndex = (int)PoolResetMode.PoolableCallbacks;
|
||||
property.FindPropertyRelative("minRetained").intValue = 0;
|
||||
property.FindPropertyRelative("category").enumValueIndex = (int)PoolCategory.Default;
|
||||
property.FindPropertyRelative("softCapacity").intValue = 8;
|
||||
property.FindPropertyRelative("hardCapacity").intValue = 8;
|
||||
property.FindPropertyRelative("idleTrimDelay").floatValue = 30f;
|
||||
property.FindPropertyRelative("prefabUnloadDelay").floatValue = 60f;
|
||||
property.FindPropertyRelative("autoRecycleDelay").floatValue = 0f;
|
||||
property.FindPropertyRelative("trimBatchPerTick").intValue = 2;
|
||||
property.FindPropertyRelative("warmupBatchPerFrame").intValue = 2;
|
||||
property.FindPropertyRelative("warmupFrameBudgetMs").floatValue = 1f;
|
||||
property.FindPropertyRelative("allowRuntimeExpand").boolValue = false;
|
||||
property.FindPropertyRelative("keepPrefabResident").boolValue = false;
|
||||
property.FindPropertyRelative("aggressiveTrimOnLowMemory").boolValue = false;
|
||||
property.FindPropertyRelative("hardCapacity").intValue = 16;
|
||||
property.FindPropertyRelative("priority").intValue = index;
|
||||
}
|
||||
|
||||
private void RefreshEntryPriorities()
|
||||
{
|
||||
if (_entriesProperty == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int count = _entriesProperty.arraySize;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
SerializedProperty entry = _entriesProperty.GetArrayElementAtIndex(i);
|
||||
entry.FindPropertyRelative("priority").intValue = count - i;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetAssetGuid(PoolConfigScriptableObject asset)
|
||||
{
|
||||
if (asset == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string assetPath = AssetDatabase.GetAssetPath(asset);
|
||||
return string.IsNullOrEmpty(assetPath) ? string.Empty : AssetDatabase.AssetPathToGUID(assetPath);
|
||||
}
|
||||
|
||||
private void SaveAsset()
|
||||
{
|
||||
if (_asset == null || _serializedObject == null)
|
||||
@ -526,10 +669,10 @@ namespace AlicizaX
|
||||
|
||||
private void RefreshTitle()
|
||||
{
|
||||
string assetLabel = _asset == null ? "No PoolConfig Selected" : _asset.name;
|
||||
string assetLabel = _asset == null ? "未选择 PoolConfig" : _asset.name;
|
||||
if (_hasUnsavedChanges)
|
||||
{
|
||||
assetLabel += " *";
|
||||
assetLabel = Utility.Text.Format("{0} *", assetLabel);
|
||||
}
|
||||
|
||||
_titleLabel.text = assetLabel;
|
||||
@ -557,7 +700,7 @@ namespace AlicizaX
|
||||
{
|
||||
if (property == null)
|
||||
{
|
||||
return "<Missing>";
|
||||
return "<规则缺失>";
|
||||
}
|
||||
|
||||
string entryName = property.FindPropertyRelative("entryName").stringValue;
|
||||
@ -567,19 +710,59 @@ namespace AlicizaX
|
||||
return entryName;
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(assetPath) ? "<Unnamed Entry>" : assetPath;
|
||||
return string.IsNullOrWhiteSpace(assetPath) ? "<未命名规则>" : assetPath;
|
||||
}
|
||||
|
||||
private string GetSecondaryLabel(SerializedProperty property)
|
||||
private static List<string> GetEnumOptions(string propertyName)
|
||||
{
|
||||
if (property == null)
|
||||
return propertyName switch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
"matchMode" => MatchModeOptions,
|
||||
"loaderType" => LoaderTypeOptions,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
string group = property.FindPropertyRelative("group").stringValue;
|
||||
string assetPath = property.FindPropertyRelative("assetPath").stringValue;
|
||||
return string.IsNullOrWhiteSpace(assetPath) ? group : $"{group} | {assetPath}";
|
||||
private static bool ShouldDisplayField(string propertyName)
|
||||
{
|
||||
return propertyName != "category";
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyField(string propertyName)
|
||||
{
|
||||
return propertyName == "priority";
|
||||
}
|
||||
|
||||
private static string GetFieldLabel(string propertyName)
|
||||
{
|
||||
return propertyName switch
|
||||
{
|
||||
"entryName" => "规则名称",
|
||||
"group" => "分组",
|
||||
"assetPath" => "资源路径",
|
||||
"matchMode" => "匹配模式",
|
||||
"loaderType" => "加载器类型",
|
||||
"softCapacity" => "软容量",
|
||||
"hardCapacity" => "容量",
|
||||
"priority" => "优先级",
|
||||
_ => propertyName
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetFieldDescription(string propertyName)
|
||||
{
|
||||
return propertyName switch
|
||||
{
|
||||
"entryName" => "规则名称就是主定位信息。列表、调试和问题排查都直接看这个名字。",
|
||||
"group" => "用于 GameObjectPoolManager 下的空闲节点归类。不填或空值会自动回落到 DefaultGroup。",
|
||||
"assetPath" => "要匹配的资源路径。精确匹配填完整路径,前缀匹配可填写目录前缀。",
|
||||
"matchMode" => "精确匹配只命中单一路径,前缀匹配适合同目录或同类资源共用规则。",
|
||||
"loaderType" => "决定 Prefab 从哪个资源通道加载。AssetBundle 走包体资源,Resources 走内置目录。",
|
||||
"softCapacity" => "超过该值后,维护阶段会优先回收空闲实例。",
|
||||
"hardCapacity" => "基础容量。超过这个值会自动扩容并输出警告,后续维护回收会再收回到这个基准。",
|
||||
"priority" => "由左侧拖拽顺序自动维护,越靠上优先级越高。",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private void ClampSelection()
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AlicizaX
|
||||
{
|
||||
@ -8,10 +7,7 @@ namespace AlicizaX
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (GUILayout.Button("Open Editor", GUILayout.Width(160f), GUILayout.Height(28f)))
|
||||
{
|
||||
PoolConfigEditorWindow.Open((PoolConfigScriptableObject)target);
|
||||
}
|
||||
DrawDefaultInspector();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AlicizaX
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class PoolAutoRecycleAfterSeconds : MonoBehaviour, IPoolAutoRecycle
|
||||
{
|
||||
[Min(0f)]
|
||||
public float delaySeconds = 1f;
|
||||
|
||||
public bool TryGetAutoRecycleDelay(out float delay)
|
||||
{
|
||||
delay = delaySeconds;
|
||||
return delay > 0f;
|
||||
}
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class PoolAutoRecycleParticleStop : MonoBehaviour, IGameObjectPoolable
|
||||
{
|
||||
@ -76,168 +61,18 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class PoolResetRigidbodyState : MonoBehaviour, IPoolResettablePhysics
|
||||
{
|
||||
[SerializeField] private bool includeChildren = true;
|
||||
private Rigidbody[] _rigidbodies3D;
|
||||
private Rigidbody2D[] _rigidbodies2D;
|
||||
|
||||
private void EnsureCache()
|
||||
{
|
||||
if (_rigidbodies3D == null)
|
||||
{
|
||||
_rigidbodies3D = includeChildren ? GetComponentsInChildren<Rigidbody>(true) : GetComponents<Rigidbody>();
|
||||
}
|
||||
|
||||
if (_rigidbodies2D == null)
|
||||
{
|
||||
_rigidbodies2D = includeChildren ? GetComponentsInChildren<Rigidbody2D>(true) : GetComponents<Rigidbody2D>();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetPhysicsState()
|
||||
{
|
||||
EnsureCache();
|
||||
|
||||
for (int i = 0; i < _rigidbodies3D.Length; i++)
|
||||
{
|
||||
Rigidbody body = _rigidbodies3D[i];
|
||||
if (body == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
body.velocity = Vector3.zero;
|
||||
body.angularVelocity = Vector3.zero;
|
||||
body.Sleep();
|
||||
}
|
||||
|
||||
for (int i = 0; i < _rigidbodies2D.Length; i++)
|
||||
{
|
||||
Rigidbody2D body = _rigidbodies2D[i];
|
||||
if (body == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
body.velocity = Vector2.zero;
|
||||
body.angularVelocity = 0f;
|
||||
body.Sleep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class PoolResetParticleSystems : MonoBehaviour, IPoolResettableVisual
|
||||
{
|
||||
[SerializeField] private bool includeChildren = true;
|
||||
private ParticleSystem[] _particleSystems;
|
||||
|
||||
private void EnsureCache()
|
||||
{
|
||||
if (_particleSystems == null)
|
||||
{
|
||||
_particleSystems = includeChildren
|
||||
? GetComponentsInChildren<ParticleSystem>(true)
|
||||
: GetComponents<ParticleSystem>();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetVisualState()
|
||||
{
|
||||
EnsureCache();
|
||||
|
||||
for (int i = 0; i < _particleSystems.Length; i++)
|
||||
{
|
||||
ParticleSystem particle = _particleSystems[i];
|
||||
if (particle == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
particle.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
particle.Clear(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class PoolResetTrailRenderers : MonoBehaviour, IPoolResettableVisual
|
||||
{
|
||||
[SerializeField] private bool includeChildren = true;
|
||||
private TrailRenderer[] _trailRenderers;
|
||||
|
||||
private void EnsureCache()
|
||||
{
|
||||
if (_trailRenderers == null)
|
||||
{
|
||||
_trailRenderers = includeChildren
|
||||
? GetComponentsInChildren<TrailRenderer>(true)
|
||||
: GetComponents<TrailRenderer>();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetVisualState()
|
||||
{
|
||||
EnsureCache();
|
||||
|
||||
for (int i = 0; i < _trailRenderers.Length; i++)
|
||||
{
|
||||
TrailRenderer trail = _trailRenderers[i];
|
||||
if (trail == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
trail.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class PoolResetAnimatorState : MonoBehaviour, IPoolResettableAnimation
|
||||
{
|
||||
[SerializeField] private bool includeChildren = true;
|
||||
private Animator[] _animators;
|
||||
|
||||
private void EnsureCache()
|
||||
{
|
||||
if (_animators == null)
|
||||
{
|
||||
_animators = includeChildren ? GetComponentsInChildren<Animator>(true) : GetComponents<Animator>();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetAnimationState()
|
||||
{
|
||||
EnsureCache();
|
||||
|
||||
for (int i = 0; i < _animators.Length; i++)
|
||||
{
|
||||
Animator animator = _animators[i];
|
||||
if (animator == null || !animator.isActiveAndEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
animator.Rebind();
|
||||
animator.Update(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class PoolSleepableGameObjectGroup : MonoBehaviour, IPoolSleepable
|
||||
{
|
||||
[SerializeField] private List<Behaviour> disableOnSleep = new List<Behaviour>();
|
||||
[SerializeField] private List<Renderer> hideOnSleep = new List<Renderer>();
|
||||
[SerializeField] private List<Collider> disableCollider3D = new List<Collider>();
|
||||
[SerializeField] private List<Collider2D> disableCollider2D = new List<Collider2D>();
|
||||
[SerializeField] private Behaviour[] disableOnSleep;
|
||||
[SerializeField] private Renderer[] hideOnSleep;
|
||||
[SerializeField] private Collider[] disableCollider3D;
|
||||
[SerializeField] private Collider2D[] disableCollider2D;
|
||||
|
||||
public void EnterSleep()
|
||||
{
|
||||
for (int i = 0; i < disableOnSleep.Count; i++)
|
||||
int disableCount = disableOnSleep == null ? 0 : disableOnSleep.Length;
|
||||
for (int i = 0; i < disableCount; i++)
|
||||
{
|
||||
if (disableOnSleep[i] != null)
|
||||
{
|
||||
@ -245,7 +80,8 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < hideOnSleep.Count; i++)
|
||||
int hideCount = hideOnSleep == null ? 0 : hideOnSleep.Length;
|
||||
for (int i = 0; i < hideCount; i++)
|
||||
{
|
||||
if (hideOnSleep[i] != null)
|
||||
{
|
||||
@ -253,7 +89,8 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < disableCollider3D.Count; i++)
|
||||
int collider3DCount = disableCollider3D == null ? 0 : disableCollider3D.Length;
|
||||
for (int i = 0; i < collider3DCount; i++)
|
||||
{
|
||||
if (disableCollider3D[i] != null)
|
||||
{
|
||||
@ -261,7 +98,8 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < disableCollider2D.Count; i++)
|
||||
int collider2DCount = disableCollider2D == null ? 0 : disableCollider2D.Length;
|
||||
for (int i = 0; i < collider2DCount; i++)
|
||||
{
|
||||
if (disableCollider2D[i] != null)
|
||||
{
|
||||
@ -272,7 +110,8 @@ namespace AlicizaX
|
||||
|
||||
public void ExitSleep(in PoolSpawnContext context)
|
||||
{
|
||||
for (int i = 0; i < disableOnSleep.Count; i++)
|
||||
int disableCount = disableOnSleep == null ? 0 : disableOnSleep.Length;
|
||||
for (int i = 0; i < disableCount; i++)
|
||||
{
|
||||
if (disableOnSleep[i] != null)
|
||||
{
|
||||
@ -280,7 +119,8 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < hideOnSleep.Count; i++)
|
||||
int hideCount = hideOnSleep == null ? 0 : hideOnSleep.Length;
|
||||
for (int i = 0; i < hideCount; i++)
|
||||
{
|
||||
if (hideOnSleep[i] != null)
|
||||
{
|
||||
@ -288,7 +128,8 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < disableCollider3D.Count; i++)
|
||||
int collider3DCount = disableCollider3D == null ? 0 : disableCollider3D.Length;
|
||||
for (int i = 0; i < collider3DCount; i++)
|
||||
{
|
||||
if (disableCollider3D[i] != null)
|
||||
{
|
||||
@ -296,7 +137,8 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < disableCollider2D.Count; i++)
|
||||
int collider2DCount = disableCollider2D == null ? 0 : disableCollider2D.Length;
|
||||
for (int i = 0; i < collider2DCount; i++)
|
||||
{
|
||||
if (disableCollider2D[i] != null)
|
||||
{
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AlicizaX.ObjectPool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AlicizaX
|
||||
@ -16,84 +16,34 @@ namespace AlicizaX
|
||||
Prefix = 1
|
||||
}
|
||||
|
||||
public enum PoolOverflowPolicy
|
||||
public enum PoolCategory
|
||||
{
|
||||
FailFast = 0,
|
||||
InstantiateOneShot = 1,
|
||||
AutoExpand = 2,
|
||||
RecycleOldestInactive = 3,
|
||||
DropNewestRequest = 4
|
||||
}
|
||||
|
||||
public enum PoolTrimPolicy
|
||||
{
|
||||
None = 0,
|
||||
IdleOnly = 1,
|
||||
IdleAndPriority = 2,
|
||||
AggressiveOnLowMemory = 3
|
||||
}
|
||||
|
||||
public enum PoolActivationMode
|
||||
{
|
||||
SetActive = 0,
|
||||
SleepWake = 1,
|
||||
Custom = 2
|
||||
}
|
||||
|
||||
public enum PoolResetMode
|
||||
{
|
||||
TransformOnly = 0,
|
||||
PoolableCallbacks = 1,
|
||||
FullReset = 2,
|
||||
Custom = 3
|
||||
Default = 0,
|
||||
Effect = 1,
|
||||
Monster = 2,
|
||||
Building = 3,
|
||||
UI = 4,
|
||||
Custom = 5
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class PoolEntry
|
||||
{
|
||||
public const string DefaultGroup = "Default";
|
||||
public const string DefaultEntryName = "DefaultPool";
|
||||
public const string DefaultGroup = "DefaultGroup";
|
||||
public const string DefaultEntryName = "PoolRule";
|
||||
|
||||
public string entryName = DefaultEntryName;
|
||||
public string group = DefaultGroup;
|
||||
public string assetPath;
|
||||
public string assetPath = string.Empty;
|
||||
public PoolMatchMode matchMode = PoolMatchMode.Exact;
|
||||
public PoolResourceLoaderType loaderType = PoolResourceLoaderType.AssetBundle;
|
||||
public PoolOverflowPolicy overflowPolicy = PoolOverflowPolicy.FailFast;
|
||||
public PoolTrimPolicy trimPolicy = PoolTrimPolicy.IdleOnly;
|
||||
public PoolActivationMode activationMode = PoolActivationMode.SetActive;
|
||||
public PoolResetMode resetMode = PoolResetMode.PoolableCallbacks;
|
||||
|
||||
[Min(0)]
|
||||
public int minRetained = 0;
|
||||
public PoolCategory category = PoolCategory.Default;
|
||||
|
||||
[Min(1)]
|
||||
public int softCapacity = 8;
|
||||
|
||||
[Min(1)]
|
||||
public int hardCapacity = 8;
|
||||
|
||||
[Min(0f)]
|
||||
public float idleTrimDelay = 30f;
|
||||
|
||||
[Min(0f)]
|
||||
public float prefabUnloadDelay = 60f;
|
||||
|
||||
[Min(0f)]
|
||||
public float autoRecycleDelay = 0f;
|
||||
|
||||
[Min(1)]
|
||||
public int trimBatchPerTick = 2;
|
||||
|
||||
[Min(1)]
|
||||
public int warmupBatchPerFrame = 2;
|
||||
|
||||
[Min(0f)]
|
||||
public float warmupFrameBudgetMs = 1f;
|
||||
|
||||
public bool allowRuntimeExpand;
|
||||
public bool keepPrefabResident;
|
||||
public bool aggressiveTrimOnLowMemory;
|
||||
public int hardCapacity = 16;
|
||||
public int priority;
|
||||
|
||||
public void Normalize()
|
||||
@ -101,43 +51,26 @@ namespace AlicizaX
|
||||
entryName = string.IsNullOrWhiteSpace(entryName) ? DefaultEntryName : entryName.Trim();
|
||||
group = string.IsNullOrWhiteSpace(group) ? DefaultGroup : group.Trim();
|
||||
assetPath = NormalizeAssetPath(assetPath);
|
||||
minRetained = Mathf.Max(0, minRetained);
|
||||
softCapacity = Mathf.Max(1, softCapacity);
|
||||
hardCapacity = Mathf.Max(softCapacity, hardCapacity);
|
||||
minRetained = Mathf.Min(minRetained, hardCapacity);
|
||||
idleTrimDelay = Mathf.Max(0f, idleTrimDelay);
|
||||
prefabUnloadDelay = Mathf.Max(0f, prefabUnloadDelay);
|
||||
autoRecycleDelay = Mathf.Max(0f, autoRecycleDelay);
|
||||
trimBatchPerTick = Mathf.Max(1, trimBatchPerTick);
|
||||
warmupBatchPerFrame = Mathf.Max(1, warmupBatchPerFrame);
|
||||
warmupFrameBudgetMs = Mathf.Max(0f, warmupFrameBudgetMs);
|
||||
}
|
||||
|
||||
public bool Matches(string requestedAssetPath, string requestedGroup = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(assetPath) || string.IsNullOrWhiteSpace(requestedAssetPath))
|
||||
if (string.IsNullOrEmpty(assetPath) || string.IsNullOrEmpty(requestedAssetPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(requestedGroup) &&
|
||||
if (!string.IsNullOrEmpty(requestedGroup) &&
|
||||
!string.Equals(group, requestedGroup, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return matchMode switch
|
||||
{
|
||||
PoolMatchMode.Exact => string.Equals(requestedAssetPath, assetPath, StringComparison.Ordinal),
|
||||
PoolMatchMode.Prefix => requestedAssetPath.StartsWith(assetPath, StringComparison.Ordinal),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public string BuildResolvedPoolKey(string resolvedAssetPath)
|
||||
{
|
||||
string concretePath = NormalizeAssetPath(resolvedAssetPath);
|
||||
return $"{group}|{(int)matchMode}|{assetPath}|{(int)loaderType}|{concretePath}|{entryName}";
|
||||
return matchMode == PoolMatchMode.Exact
|
||||
? string.Equals(requestedAssetPath, assetPath, StringComparison.Ordinal)
|
||||
: requestedAssetPath.StartsWith(assetPath, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public static int CompareByPriority(PoolEntry left, PoolEntry right)
|
||||
@ -169,8 +102,8 @@ namespace AlicizaX
|
||||
return modeCompare;
|
||||
}
|
||||
|
||||
int leftLength = left.assetPath?.Length ?? 0;
|
||||
int rightLength = right.assetPath?.Length ?? 0;
|
||||
int leftLength = left.assetPath == null ? 0 : left.assetPath.Length;
|
||||
int rightLength = right.assetPath == null ? 0 : right.assetPath.Length;
|
||||
int pathLengthCompare = rightLength.CompareTo(leftLength);
|
||||
if (pathLengthCompare != 0)
|
||||
{
|
||||
@ -191,81 +124,344 @@ namespace AlicizaX
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class ResolvedPoolConfig
|
||||
internal struct PoolCompiledRule
|
||||
{
|
||||
public int ruleIndex;
|
||||
public string entryName;
|
||||
public string group;
|
||||
public string assetPath;
|
||||
public PoolMatchMode matchMode;
|
||||
public PoolResourceLoaderType loaderType;
|
||||
public PoolOverflowPolicy overflowPolicy;
|
||||
public PoolTrimPolicy trimPolicy;
|
||||
public PoolActivationMode activationMode;
|
||||
public PoolResetMode resetMode;
|
||||
public int minRetained;
|
||||
public PoolCategory category;
|
||||
public int softCapacity;
|
||||
public int hardCapacity;
|
||||
public float idleTrimDelay;
|
||||
public float prefabUnloadDelay;
|
||||
public float autoRecycleDelay;
|
||||
public int trimBatchPerTick;
|
||||
public int warmupBatchPerFrame;
|
||||
public float warmupFrameBudgetMs;
|
||||
public bool allowRuntimeExpand;
|
||||
public bool keepPrefabResident;
|
||||
public bool aggressiveTrimOnLowMemory;
|
||||
public int priority;
|
||||
|
||||
public string BuildResolvedPoolKey(string resolvedAssetPath)
|
||||
{
|
||||
string concretePath = PoolEntry.NormalizeAssetPath(resolvedAssetPath);
|
||||
return $"{group}|{(int)matchMode}|{assetPath}|{(int)loaderType}|{concretePath}|{entryName}";
|
||||
}
|
||||
public bool IsPrefix => matchMode == PoolMatchMode.Prefix;
|
||||
|
||||
public static ResolvedPoolConfig From(PoolEntry entry)
|
||||
public static PoolCompiledRule FromEntry(PoolEntry entry, int ruleIndex)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entry));
|
||||
}
|
||||
|
||||
return new ResolvedPoolConfig
|
||||
return new PoolCompiledRule
|
||||
{
|
||||
ruleIndex = ruleIndex,
|
||||
entryName = entry.entryName,
|
||||
group = entry.group,
|
||||
assetPath = entry.assetPath,
|
||||
matchMode = entry.matchMode,
|
||||
loaderType = entry.loaderType,
|
||||
overflowPolicy = entry.overflowPolicy,
|
||||
trimPolicy = entry.trimPolicy,
|
||||
activationMode = entry.activationMode,
|
||||
resetMode = entry.resetMode,
|
||||
minRetained = entry.minRetained,
|
||||
category = entry.category,
|
||||
softCapacity = entry.softCapacity,
|
||||
hardCapacity = entry.hardCapacity,
|
||||
idleTrimDelay = entry.idleTrimDelay,
|
||||
prefabUnloadDelay = entry.prefabUnloadDelay,
|
||||
autoRecycleDelay = entry.autoRecycleDelay,
|
||||
trimBatchPerTick = entry.trimBatchPerTick,
|
||||
warmupBatchPerFrame = entry.warmupBatchPerFrame,
|
||||
warmupFrameBudgetMs = entry.warmupFrameBudgetMs,
|
||||
allowRuntimeExpand = entry.allowRuntimeExpand,
|
||||
keepPrefabResident = entry.keepPrefabResident,
|
||||
aggressiveTrimOnLowMemory = entry.aggressiveTrimOnLowMemory,
|
||||
priority = entry.priority
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class PoolConfigCatalog
|
||||
internal sealed class PoolCompiledCatalog
|
||||
{
|
||||
public readonly List<PoolEntry> entries;
|
||||
private readonly PoolCompiledRule[] _rules;
|
||||
private StringOpenHashMap _groupIndexMap;
|
||||
private PoolCompiledGroup[] _groups;
|
||||
private StringOpenHashMap _globalExactMap;
|
||||
private PoolPrefixTrie _globalPrefixTrie;
|
||||
|
||||
public PoolConfigCatalog(List<PoolEntry> entries)
|
||||
private PoolCompiledCatalog(
|
||||
PoolCompiledRule[] rules,
|
||||
StringOpenHashMap groupIndexMap,
|
||||
PoolCompiledGroup[] groups,
|
||||
StringOpenHashMap globalExactMap,
|
||||
PoolPrefixTrie globalPrefixTrie)
|
||||
{
|
||||
this.entries = entries ?? throw new ArgumentNullException(nameof(entries));
|
||||
_rules = rules;
|
||||
_groupIndexMap = groupIndexMap;
|
||||
_groups = groups;
|
||||
_globalExactMap = globalExactMap;
|
||||
_globalPrefixTrie = globalPrefixTrie;
|
||||
}
|
||||
|
||||
public bool IsEmpty => _rules == null || _rules.Length == 0;
|
||||
|
||||
public int RuleCount => _rules == null ? 0 : _rules.Length;
|
||||
|
||||
public ref readonly PoolCompiledRule GetRule(int ruleIndex)
|
||||
{
|
||||
return ref _rules[ruleIndex];
|
||||
}
|
||||
|
||||
public int Resolve(string assetPath, string group)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetPath) || _rules == null || _rules.Length == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(group))
|
||||
{
|
||||
if (_groupIndexMap.TryGetValue(group, out int groupIndex))
|
||||
{
|
||||
return _groups[groupIndex].Resolve(assetPath);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_globalExactMap.TryGetValue(assetPath, out int exactRuleIndex))
|
||||
{
|
||||
return exactRuleIndex;
|
||||
}
|
||||
|
||||
return _globalPrefixTrie.Resolve(assetPath);
|
||||
}
|
||||
|
||||
public static PoolCompiledCatalog Empty()
|
||||
{
|
||||
return new PoolCompiledCatalog(
|
||||
Array.Empty<PoolCompiledRule>(),
|
||||
new StringOpenHashMap(8),
|
||||
Array.Empty<PoolCompiledGroup>(),
|
||||
new StringOpenHashMap(8),
|
||||
new PoolPrefixTrie(0));
|
||||
}
|
||||
|
||||
public static PoolCompiledCatalog Build(PoolEntry[] entries)
|
||||
{
|
||||
if (entries == null || entries.Length == 0)
|
||||
{
|
||||
return Empty();
|
||||
}
|
||||
|
||||
int entryCount = entries.Length;
|
||||
var groupIndexMap = new StringOpenHashMap(entryCount);
|
||||
var groupNames = new string[entryCount];
|
||||
var groupPrefixChars = new int[entryCount];
|
||||
int groupCount = 0;
|
||||
int globalPrefixChars = 0;
|
||||
|
||||
for (int i = 0; i < entryCount; i++)
|
||||
{
|
||||
PoolEntry entry = entries[i];
|
||||
if (entry == null || string.IsNullOrEmpty(entry.assetPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!groupIndexMap.TryGetValue(entry.group, out int groupIndex))
|
||||
{
|
||||
groupIndex = groupCount;
|
||||
groupIndexMap.AddOrUpdate(entry.group, groupIndex);
|
||||
groupNames[groupCount] = entry.group;
|
||||
groupCount++;
|
||||
}
|
||||
|
||||
if (entry.matchMode == PoolMatchMode.Prefix)
|
||||
{
|
||||
int pathLength = entry.assetPath.Length;
|
||||
groupPrefixChars[groupIndex] += pathLength;
|
||||
globalPrefixChars += pathLength;
|
||||
}
|
||||
}
|
||||
|
||||
var groups = new PoolCompiledGroup[groupCount];
|
||||
for (int i = 0; i < groupCount; i++)
|
||||
{
|
||||
groups[i] = new PoolCompiledGroup(groupNames[i], entryCount, groupPrefixChars[i]);
|
||||
}
|
||||
|
||||
var rules = new PoolCompiledRule[entryCount];
|
||||
var globalExactMap = new StringOpenHashMap(entryCount);
|
||||
var globalPrefixTrie = new PoolPrefixTrie(globalPrefixChars);
|
||||
|
||||
for (int i = 0; i < entryCount; i++)
|
||||
{
|
||||
PoolEntry entry = entries[i];
|
||||
if (entry == null || string.IsNullOrEmpty(entry.assetPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PoolCompiledRule rule = PoolCompiledRule.FromEntry(entry, i);
|
||||
rules[i] = rule;
|
||||
|
||||
groupIndexMap.TryGetValue(rule.group, out int groupIndex);
|
||||
groups[groupIndex].Register(rule);
|
||||
|
||||
if (rule.matchMode == PoolMatchMode.Exact)
|
||||
{
|
||||
if (!globalExactMap.TryGetValue(rule.assetPath, out _))
|
||||
{
|
||||
globalExactMap.AddOrUpdate(rule.assetPath, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
globalPrefixTrie.Register(rule.assetPath, i);
|
||||
}
|
||||
}
|
||||
|
||||
return new PoolCompiledCatalog(rules, groupIndexMap, groups, globalExactMap, globalPrefixTrie);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PoolCompiledGroup
|
||||
{
|
||||
private readonly string _name;
|
||||
private StringOpenHashMap _exactMap;
|
||||
private PoolPrefixTrie _prefixTrie;
|
||||
|
||||
public PoolCompiledGroup(string name, int exactCapacity, int prefixChars)
|
||||
{
|
||||
_name = name;
|
||||
_exactMap = new StringOpenHashMap(exactCapacity);
|
||||
_prefixTrie = new PoolPrefixTrie(prefixChars);
|
||||
}
|
||||
|
||||
public void Register(in PoolCompiledRule rule)
|
||||
{
|
||||
if (rule.matchMode == PoolMatchMode.Exact)
|
||||
{
|
||||
if (!_exactMap.TryGetValue(rule.assetPath, out _))
|
||||
{
|
||||
_exactMap.AddOrUpdate(rule.assetPath, rule.ruleIndex);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_prefixTrie.Register(rule.assetPath, rule.ruleIndex);
|
||||
}
|
||||
|
||||
public int Resolve(string assetPath)
|
||||
{
|
||||
if (_exactMap.TryGetValue(assetPath, out int exactRuleIndex))
|
||||
{
|
||||
return exactRuleIndex;
|
||||
}
|
||||
|
||||
return _prefixTrie.Resolve(assetPath);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PoolPrefixTrie
|
||||
{
|
||||
private struct Node
|
||||
{
|
||||
public char character;
|
||||
public int firstChild;
|
||||
public int nextSibling;
|
||||
public int ruleIndex;
|
||||
}
|
||||
|
||||
private Node[] _nodes;
|
||||
private int _nodeCount;
|
||||
|
||||
public PoolPrefixTrie(int prefixCharCount)
|
||||
{
|
||||
int capacity = Mathf.Max(1, prefixCharCount + 1);
|
||||
_nodes = new Node[capacity];
|
||||
_nodes[0].firstChild = -1;
|
||||
_nodes[0].nextSibling = -1;
|
||||
_nodes[0].ruleIndex = -1;
|
||||
_nodeCount = 1;
|
||||
}
|
||||
|
||||
public void Register(string prefix, int ruleIndex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(prefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int nodeIndex = 0;
|
||||
int length = prefix.Length;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
nodeIndex = GetOrCreateChild(nodeIndex, prefix[i]);
|
||||
}
|
||||
|
||||
if (_nodes[nodeIndex].ruleIndex < 0)
|
||||
{
|
||||
_nodes[nodeIndex].ruleIndex = ruleIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public int Resolve(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value) || _nodes == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int nodeIndex = 0;
|
||||
int bestRuleIndex = -1;
|
||||
int length = value.Length;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
nodeIndex = FindChild(nodeIndex, value[i]);
|
||||
if (nodeIndex < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int matchedRuleIndex = _nodes[nodeIndex].ruleIndex;
|
||||
if (matchedRuleIndex >= 0)
|
||||
{
|
||||
bestRuleIndex = matchedRuleIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return bestRuleIndex;
|
||||
}
|
||||
|
||||
private int GetOrCreateChild(int nodeIndex, char character)
|
||||
{
|
||||
int childIndex = _nodes[nodeIndex].firstChild;
|
||||
while (childIndex >= 0)
|
||||
{
|
||||
if (_nodes[childIndex].character == character)
|
||||
{
|
||||
return childIndex;
|
||||
}
|
||||
|
||||
childIndex = _nodes[childIndex].nextSibling;
|
||||
}
|
||||
|
||||
EnsureCapacity(_nodeCount + 1);
|
||||
int newNodeIndex = _nodeCount++;
|
||||
_nodes[newNodeIndex].character = character;
|
||||
_nodes[newNodeIndex].firstChild = -1;
|
||||
_nodes[newNodeIndex].nextSibling = _nodes[nodeIndex].firstChild;
|
||||
_nodes[newNodeIndex].ruleIndex = -1;
|
||||
_nodes[nodeIndex].firstChild = newNodeIndex;
|
||||
return newNodeIndex;
|
||||
}
|
||||
|
||||
private int FindChild(int nodeIndex, char character)
|
||||
{
|
||||
int childIndex = _nodes[nodeIndex].firstChild;
|
||||
while (childIndex >= 0)
|
||||
{
|
||||
if (_nodes[childIndex].character == character)
|
||||
{
|
||||
return childIndex;
|
||||
}
|
||||
|
||||
childIndex = _nodes[childIndex].nextSibling;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int required)
|
||||
{
|
||||
if (_nodes.Length >= required)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int newCapacity = Mathf.Max(required, _nodes.Length << 1);
|
||||
var newNodes = new Node[newCapacity];
|
||||
Array.Copy(_nodes, 0, newNodes, 0, _nodeCount);
|
||||
_nodes = newNodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AlicizaX
|
||||
{
|
||||
[CreateAssetMenu(fileName = "PoolConfig", menuName = "GameplaySystem/PoolConfig", order = 10)]
|
||||
public class PoolConfigScriptableObject : ScriptableObject
|
||||
public sealed class PoolConfigScriptableObject : ScriptableObject
|
||||
{
|
||||
public List<PoolEntry> entries = new List<PoolEntry>();
|
||||
|
||||
public PoolConfigCatalog BuildCatalog()
|
||||
internal PoolCompiledCatalog BuildCatalog()
|
||||
{
|
||||
Normalize();
|
||||
|
||||
var normalizedEntries = new List<PoolEntry>(entries.Count);
|
||||
if (entries == null || entries.Count == 0)
|
||||
{
|
||||
return PoolCompiledCatalog.Empty();
|
||||
}
|
||||
|
||||
int validCount = 0;
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
PoolEntry entry = entries[i];
|
||||
if (entry == null)
|
||||
if (entry != null && !string.IsNullOrEmpty(entry.assetPath))
|
||||
{
|
||||
validCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (validCount == 0)
|
||||
{
|
||||
return PoolCompiledCatalog.Empty();
|
||||
}
|
||||
|
||||
var normalizedEntries = new PoolEntry[validCount];
|
||||
int writeIndex = 0;
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
PoolEntry entry = entries[i];
|
||||
if (entry == null || string.IsNullOrEmpty(entry.assetPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
normalizedEntries.Add(entry);
|
||||
normalizedEntries[writeIndex++] = entry;
|
||||
}
|
||||
|
||||
normalizedEntries.Sort(PoolEntry.CompareByPriority);
|
||||
return new PoolConfigCatalog(normalizedEntries);
|
||||
Array.Sort(normalizedEntries, PoolEntry.CompareByPriority);
|
||||
return PoolCompiledCatalog.Build(normalizedEntries);
|
||||
}
|
||||
|
||||
public void Normalize()
|
||||
@ -33,6 +55,7 @@ namespace AlicizaX
|
||||
if (entries == null)
|
||||
{
|
||||
entries = new List<PoolEntry>();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user