|
1 | 1 | using System; |
2 | 2 | using System.Collections.Generic; |
3 | 3 | using System.Linq; |
4 | | -using System.Text.RegularExpressions; |
5 | 4 | using TMPro; |
6 | | -using UnityEngine.Pool; |
7 | 5 |
|
8 | 6 | namespace BP.TMPA |
9 | 7 | { |
10 | 8 | internal class TextMeshPreprocessor : ITextPreprocessor, IDisposable |
11 | 9 | { |
12 | | - private const string tagPattern = @"<(\/?)([A-Za-z0-9]+)(=[^>]+)?(\/?)>"; |
13 | | - |
14 | 10 | // Caching mechanisms |
15 | 11 | private string lastInputText; |
16 | 12 | private string processedText; |
| 13 | + private readonly TagValidator tagValidator; |
17 | 14 |
|
18 | | - // Object pooling for TagData |
19 | | - private readonly ObjectPool<TextTagData> tagDataPool; |
20 | | - private readonly ObjectPool<List<TextTagData>> tagListPool; |
21 | | - |
22 | | - public Dictionary<int, List<TextTagData>> CharacterTagEffects { get; } = new(); |
23 | | - private readonly Func<string, string, bool> tagValidator; |
24 | | - |
25 | | - // Reusable collections to reduce allocations |
26 | | - private readonly Dictionary<string, Stack<TextTagData>> activeStacks = new(16); |
27 | | - |
28 | | - public TextMeshPreprocessor(Func<string, string, bool> tagValidator) |
| 15 | + public TextMeshPreprocessor(TagValidator tagValidator) |
29 | 16 | { |
30 | | - this.tagValidator = tagValidator ?? throw new ArgumentNullException(nameof(tagValidator)); |
31 | | - |
32 | | - tagDataPool = new ObjectPool<TextTagData>( |
33 | | - createFunc: () => new TextTagData(), |
34 | | - actionOnGet: (tag) => tag.Reset(), |
35 | | - actionOnRelease: (tag) => tag.Clear(), |
36 | | - defaultCapacity: 64 |
37 | | - ); |
38 | | - |
39 | | - tagListPool = new ObjectPool<List<TextTagData>>( |
40 | | - createFunc: () => new List<TextTagData>(), |
41 | | - actionOnGet: (list) => list.Clear(), |
42 | | - defaultCapacity: 64 |
43 | | - ); |
| 17 | + this.tagValidator = tagValidator; |
44 | 18 | } |
45 | 19 |
|
46 | 20 | public string PreprocessText(string inputText) |
47 | 21 | { |
48 | 22 | // Checks if the last processed text really changed |
49 | 23 | if (inputText != lastInputText) |
50 | 24 | { |
51 | | - processedText = ProcessText(inputText); |
| 25 | + processedText = TextParser.RemoveCustomTags(inputText, tagValidator); |
52 | 26 | lastInputText = inputText; |
53 | 27 | } |
54 | 28 |
|
55 | 29 | return processedText; |
56 | 30 | } |
57 | 31 |
|
58 | | - private string ProcessText(string input) |
59 | | - { |
60 | | - ReleaseResources(); |
61 | | - CharacterTagEffects.Clear(); |
62 | | - activeStacks.Clear(); |
63 | | - |
64 | | - string processedText = input; |
65 | | - var matches = Regex.Matches(input, tagPattern); |
66 | | - int charIndex = 0; |
67 | | - |
68 | | - foreach (Match match in matches.Cast<Match>()) |
69 | | - { |
70 | | - // Tag properties |
71 | | - string tag = match.Value; |
72 | | - bool isClosingTag = match.Groups[1].Value == "/"; |
73 | | - string tagName = match.Groups[2].Value; |
74 | | - string attributes = match.Groups[3].Value; |
75 | | - int tagIndex = match.Index - charIndex; |
76 | | - |
77 | | - if (tagValidator(tagName, attributes)) |
78 | | - { |
79 | | - if (isClosingTag) |
80 | | - { |
81 | | - if (!activeStacks.TryGetValue(tagName, out var activeStack)) |
82 | | - continue; |
83 | | - |
84 | | - var matchingOpenTag = activeStack.Pop(); |
85 | | - if (matchingOpenTag != null) |
86 | | - { |
87 | | - matchingOpenTag.Close(tagIndex); |
88 | | - for (int i = matchingOpenTag.StartIndex; i < matchingOpenTag.EndIndex; i++) |
89 | | - { |
90 | | - // Use pooled list for character tag effects |
91 | | - if (!CharacterTagEffects.TryGetValue(i, out var tagList)) |
92 | | - { |
93 | | - tagList = tagListPool.Get(); |
94 | | - CharacterTagEffects[i] = tagList; |
95 | | - } |
96 | | - |
97 | | - // Only add the innermost tag at each position |
98 | | - if (!tagList.Any(t => t.Name == matchingOpenTag.Name)) |
99 | | - tagList.Add(matchingOpenTag); |
100 | | - } |
101 | | - } |
102 | | - } |
103 | | - else |
104 | | - { |
105 | | - var tagData = tagDataPool.Get(); |
106 | | - tagData.Initialize(tag, tagName, attributes, tagIndex, int.MaxValue); |
107 | | - AddTagToStack(tagName, tagData); |
108 | | - } |
109 | | - |
110 | | - // Removes tag from display and offsets the range |
111 | | - processedText = processedText.Replace(tag, string.Empty); |
112 | | - charIndex += tag.Length; |
113 | | - } |
114 | | - } |
115 | | - |
116 | | - foreach (var stack in activeStacks) |
117 | | - { |
118 | | - foreach (var tag in stack.Value) |
119 | | - { |
120 | | - for (int i = tag.StartIndex; i < processedText.Length; i++) |
121 | | - { |
122 | | - // Use pooled list for character tag effects |
123 | | - if (!CharacterTagEffects.TryGetValue(i, out var tagList)) |
124 | | - { |
125 | | - tagList = tagListPool.Get(); |
126 | | - CharacterTagEffects[i] = tagList; |
127 | | - } |
128 | | - |
129 | | - if (!tagList.Any(t => t.Name == tag.Name)) |
130 | | - tagList.Add(tag); |
131 | | - } |
132 | | - } |
133 | | - } |
134 | | - |
135 | | - return processedText; |
136 | | - } |
137 | | - |
138 | | - private void AddTagToStack(string name, TextTagData data) |
| 32 | + public List<TextTagData> GetTagEffectsAtIndex(int index) |
139 | 33 | { |
140 | | - if (activeStacks.TryGetValue(name, out var stack)) |
141 | | - { |
142 | | - if (stack.Peek().RawTag != data.RawTag) |
143 | | - stack.Push(data); |
144 | | - } |
145 | | - else |
146 | | - { |
147 | | - var newStack = new Stack<TextTagData>(); |
148 | | - newStack.Push(data); |
149 | | - activeStacks.Add(name, newStack); |
150 | | - } |
| 34 | + return Array.Empty<TextTagData>().ToList(); |
151 | 35 | } |
152 | 36 |
|
153 | | - /// <summary> |
154 | | - /// Releases all tag lists back to the pool. |
155 | | - /// </summary> |
156 | | - private void ReleaseResources() |
157 | | - { |
158 | | - // Release all the tag lists before rebuilding them |
159 | | - foreach (var tagList in CharacterTagEffects.Values) |
160 | | - { |
161 | | - if (tagList != null) |
162 | | - { |
163 | | - tagListPool.Release(tagList); |
164 | | - } |
165 | | - } |
166 | | - |
167 | | - // Clear the dictionary as well |
168 | | - CharacterTagEffects.Clear(); |
169 | | - } |
170 | | - |
171 | | - public List<TextTagData> GetTagEffectsAtIndex(int index) |
| 37 | + public void ClearCache() |
172 | 38 | { |
173 | | - CharacterTagEffects.TryGetValue(index, out var tags); |
174 | | - return tags; |
| 39 | + lastInputText = string.Empty; |
| 40 | + processedText = string.Empty; |
175 | 41 | } |
176 | 42 |
|
177 | 43 | public void Dispose() |
178 | 44 | { |
179 | | - tagDataPool.Clear(); |
180 | | - tagListPool.Clear(); |
181 | | - CharacterTagEffects.Clear(); |
182 | | - } |
183 | 45 |
|
184 | | - public void ClearCache() |
185 | | - { |
186 | | - lastInputText = string.Empty; |
187 | | - processedText = string.Empty; |
188 | 46 | } |
189 | 47 | } |
190 | 48 | } |
0 commit comments