using System; using AlicizaX.ObjectPool; using UnityEngine; namespace AlicizaX { public enum PoolResourceLoaderType { AssetBundle = 0, Resources = 1 } public enum PoolMatchMode { Exact = 0, Prefix = 1 } public enum PoolCategory { Default = 0, Effect = 1, Monster = 2, Building = 3, UI = 4, Custom = 5 } [Serializable] public sealed class PoolEntry { public const string DefaultGroup = "DefaultGroup"; public const string DefaultEntryName = "PoolRule"; public string entryName = DefaultEntryName; public string group = DefaultGroup; public string assetPath = string.Empty; public PoolMatchMode matchMode = PoolMatchMode.Exact; public PoolResourceLoaderType loaderType = PoolResourceLoaderType.AssetBundle; public PoolCategory category = PoolCategory.Default; [Min(1)] public int softCapacity = 8; [Min(1)] public int hardCapacity = 16; public int priority; public void Normalize() { entryName = string.IsNullOrWhiteSpace(entryName) ? DefaultEntryName : entryName.Trim(); group = string.IsNullOrWhiteSpace(group) ? DefaultGroup : group.Trim(); assetPath = NormalizeAssetPath(assetPath); softCapacity = Mathf.Max(1, softCapacity); hardCapacity = Mathf.Max(softCapacity, hardCapacity); } public bool Matches(string requestedAssetPath, string requestedGroup = null) { if (string.IsNullOrEmpty(assetPath) || string.IsNullOrEmpty(requestedAssetPath)) { return false; } if (!string.IsNullOrEmpty(requestedGroup) && !string.Equals(group, requestedGroup, StringComparison.Ordinal)) { return false; } return matchMode == PoolMatchMode.Exact ? string.Equals(requestedAssetPath, assetPath, StringComparison.Ordinal) : requestedAssetPath.StartsWith(assetPath, StringComparison.Ordinal); } public static int CompareByPriority(PoolEntry left, PoolEntry right) { if (ReferenceEquals(left, right)) { return 0; } if (left == null) { return 1; } if (right == null) { return -1; } int priorityCompare = right.priority.CompareTo(left.priority); if (priorityCompare != 0) { return priorityCompare; } int modeCompare = left.matchMode.CompareTo(right.matchMode); if (modeCompare != 0) { return modeCompare; } 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) { return pathLengthCompare; } return string.Compare(left.group, right.group, StringComparison.Ordinal); } public static string NormalizeAssetPath(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } return value.Trim().Replace('\\', '/'); } } internal struct PoolCompiledRule { public int ruleIndex; public string entryName; public string group; public string assetPath; public PoolMatchMode matchMode; public PoolResourceLoaderType loaderType; public PoolCategory category; public int softCapacity; public int hardCapacity; public int priority; public bool IsPrefix => matchMode == PoolMatchMode.Prefix; public static PoolCompiledRule FromEntry(PoolEntry entry, int ruleIndex) { return new PoolCompiledRule { ruleIndex = ruleIndex, entryName = entry.entryName, group = entry.group, assetPath = entry.assetPath, matchMode = entry.matchMode, loaderType = entry.loaderType, category = entry.category, softCapacity = entry.softCapacity, hardCapacity = entry.hardCapacity, priority = entry.priority }; } } internal sealed class PoolCompiledCatalog { private readonly PoolCompiledRule[] _rules; private StringOpenHashMap _groupIndexMap; private PoolCompiledGroup[] _groups; private StringOpenHashMap _globalExactMap; private PoolPrefixTrie _globalPrefixTrie; private PoolCompiledCatalog( PoolCompiledRule[] rules, StringOpenHashMap groupIndexMap, PoolCompiledGroup[] groups, StringOpenHashMap globalExactMap, PoolPrefixTrie globalPrefixTrie) { _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(), new StringOpenHashMap(8), Array.Empty(), 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; } } }