468 lines
14 KiB
C#
468 lines
14 KiB
C#
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<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;
|
|
}
|
|
}
|
|
}
|