1 | /**************************************************************************/ |
2 | /* string_name.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "string_name.h" |
32 | |
33 | #include "core/os/os.h" |
34 | #include "core/string/print_string.h" |
35 | |
36 | StaticCString StaticCString::create(const char *p_ptr) { |
37 | StaticCString scs; |
38 | scs.ptr = p_ptr; |
39 | return scs; |
40 | } |
41 | |
42 | StringName::_Data *StringName::_table[STRING_TABLE_LEN]; |
43 | |
44 | StringName _scs_create(const char *p_chr, bool p_static) { |
45 | return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName()); |
46 | } |
47 | |
48 | bool StringName::configured = false; |
49 | Mutex StringName::mutex; |
50 | |
51 | #ifdef DEBUG_ENABLED |
52 | bool StringName::debug_stringname = false; |
53 | #endif |
54 | |
55 | void StringName::setup() { |
56 | ERR_FAIL_COND(configured); |
57 | for (int i = 0; i < STRING_TABLE_LEN; i++) { |
58 | _table[i] = nullptr; |
59 | } |
60 | configured = true; |
61 | } |
62 | |
63 | void StringName::cleanup() { |
64 | MutexLock lock(mutex); |
65 | |
66 | #ifdef DEBUG_ENABLED |
67 | if (unlikely(debug_stringname)) { |
68 | Vector<_Data *> data; |
69 | for (int i = 0; i < STRING_TABLE_LEN; i++) { |
70 | _Data *d = _table[i]; |
71 | while (d) { |
72 | data.push_back(d); |
73 | d = d->next; |
74 | } |
75 | } |
76 | |
77 | print_line("\nStringName reference ranking (from most to least referenced):\n" ); |
78 | |
79 | data.sort_custom<DebugSortReferences>(); |
80 | int unreferenced_stringnames = 0; |
81 | int rarely_referenced_stringnames = 0; |
82 | for (int i = 0; i < data.size(); i++) { |
83 | print_line(itos(i + 1) + ": " + data[i]->get_name() + " - " + itos(data[i]->debug_references)); |
84 | if (data[i]->debug_references == 0) { |
85 | unreferenced_stringnames += 1; |
86 | } else if (data[i]->debug_references < 5) { |
87 | rarely_referenced_stringnames += 1; |
88 | } |
89 | } |
90 | |
91 | print_line(vformat("\nOut of %d StringNames, %d StringNames were never referenced during this run (0 times) (%.2f%%)." , data.size(), unreferenced_stringnames, unreferenced_stringnames / float(data.size()) * 100)); |
92 | print_line(vformat("Out of %d StringNames, %d StringNames were rarely referenced during this run (1-4 times) (%.2f%%)." , data.size(), rarely_referenced_stringnames, rarely_referenced_stringnames / float(data.size()) * 100)); |
93 | } |
94 | #endif |
95 | int lost_strings = 0; |
96 | for (int i = 0; i < STRING_TABLE_LEN; i++) { |
97 | while (_table[i]) { |
98 | _Data *d = _table[i]; |
99 | if (d->static_count.get() != d->refcount.get()) { |
100 | lost_strings++; |
101 | |
102 | if (OS::get_singleton()->is_stdout_verbose()) { |
103 | if (d->cname) { |
104 | print_line("Orphan StringName: " + String(d->cname)); |
105 | } else { |
106 | print_line("Orphan StringName: " + String(d->name)); |
107 | } |
108 | } |
109 | } |
110 | |
111 | _table[i] = _table[i]->next; |
112 | memdelete(d); |
113 | } |
114 | } |
115 | if (lost_strings) { |
116 | print_verbose("StringName: " + itos(lost_strings) + " unclaimed string names at exit." ); |
117 | } |
118 | configured = false; |
119 | } |
120 | |
121 | void StringName::unref() { |
122 | ERR_FAIL_COND(!configured); |
123 | |
124 | if (_data && _data->refcount.unref()) { |
125 | MutexLock lock(mutex); |
126 | |
127 | if (_data->static_count.get() > 0) { |
128 | if (_data->cname) { |
129 | ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->cname)); |
130 | } else { |
131 | ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->name)); |
132 | } |
133 | } |
134 | if (_data->prev) { |
135 | _data->prev->next = _data->next; |
136 | } else { |
137 | if (_table[_data->idx] != _data) { |
138 | ERR_PRINT("BUG!" ); |
139 | } |
140 | _table[_data->idx] = _data->next; |
141 | } |
142 | |
143 | if (_data->next) { |
144 | _data->next->prev = _data->prev; |
145 | } |
146 | memdelete(_data); |
147 | } |
148 | |
149 | _data = nullptr; |
150 | } |
151 | |
152 | bool StringName::operator==(const String &p_name) const { |
153 | if (!_data) { |
154 | return (p_name.length() == 0); |
155 | } |
156 | |
157 | return (_data->get_name() == p_name); |
158 | } |
159 | |
160 | bool StringName::operator==(const char *p_name) const { |
161 | if (!_data) { |
162 | return (p_name[0] == 0); |
163 | } |
164 | |
165 | return (_data->get_name() == p_name); |
166 | } |
167 | |
168 | bool StringName::operator!=(const String &p_name) const { |
169 | return !(operator==(p_name)); |
170 | } |
171 | |
172 | bool StringName::operator!=(const char *p_name) const { |
173 | return !(operator==(p_name)); |
174 | } |
175 | |
176 | bool StringName::operator!=(const StringName &p_name) const { |
177 | // the real magic of all this mess happens here. |
178 | // this is why path comparisons are very fast |
179 | return _data != p_name._data; |
180 | } |
181 | |
182 | void StringName::operator=(const StringName &p_name) { |
183 | if (this == &p_name) { |
184 | return; |
185 | } |
186 | |
187 | unref(); |
188 | |
189 | if (p_name._data && p_name._data->refcount.ref()) { |
190 | _data = p_name._data; |
191 | } |
192 | } |
193 | |
194 | StringName::StringName(const StringName &p_name) { |
195 | _data = nullptr; |
196 | |
197 | ERR_FAIL_COND(!configured); |
198 | |
199 | if (p_name._data && p_name._data->refcount.ref()) { |
200 | _data = p_name._data; |
201 | } |
202 | } |
203 | |
204 | void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) { |
205 | mutex.lock(); |
206 | if (*ptr == StringName()) { |
207 | *ptr = StringName(p_name, true); |
208 | } |
209 | mutex.unlock(); |
210 | } |
211 | |
212 | StringName::StringName(const char *p_name, bool p_static) { |
213 | _data = nullptr; |
214 | |
215 | ERR_FAIL_COND(!configured); |
216 | |
217 | if (!p_name || p_name[0] == 0) { |
218 | return; //empty, ignore |
219 | } |
220 | |
221 | MutexLock lock(mutex); |
222 | |
223 | uint32_t hash = String::hash(p_name); |
224 | |
225 | uint32_t idx = hash & STRING_TABLE_MASK; |
226 | |
227 | _data = _table[idx]; |
228 | |
229 | while (_data) { |
230 | // compare hash first |
231 | if (_data->hash == hash && _data->get_name() == p_name) { |
232 | break; |
233 | } |
234 | _data = _data->next; |
235 | } |
236 | |
237 | if (_data && _data->refcount.ref()) { |
238 | // exists |
239 | if (p_static) { |
240 | _data->static_count.increment(); |
241 | } |
242 | #ifdef DEBUG_ENABLED |
243 | if (unlikely(debug_stringname)) { |
244 | _data->debug_references++; |
245 | } |
246 | #endif |
247 | return; |
248 | } |
249 | |
250 | _data = memnew(_Data); |
251 | _data->name = p_name; |
252 | _data->refcount.init(); |
253 | _data->static_count.set(p_static ? 1 : 0); |
254 | _data->hash = hash; |
255 | _data->idx = idx; |
256 | _data->cname = nullptr; |
257 | _data->next = _table[idx]; |
258 | _data->prev = nullptr; |
259 | |
260 | #ifdef DEBUG_ENABLED |
261 | if (unlikely(debug_stringname)) { |
262 | // Keep in memory, force static. |
263 | _data->refcount.ref(); |
264 | _data->static_count.increment(); |
265 | } |
266 | #endif |
267 | if (_table[idx]) { |
268 | _table[idx]->prev = _data; |
269 | } |
270 | _table[idx] = _data; |
271 | } |
272 | |
273 | StringName::StringName(const StaticCString &p_static_string, bool p_static) { |
274 | _data = nullptr; |
275 | |
276 | ERR_FAIL_COND(!configured); |
277 | |
278 | ERR_FAIL_COND(!p_static_string.ptr || !p_static_string.ptr[0]); |
279 | |
280 | MutexLock lock(mutex); |
281 | |
282 | uint32_t hash = String::hash(p_static_string.ptr); |
283 | |
284 | uint32_t idx = hash & STRING_TABLE_MASK; |
285 | |
286 | _data = _table[idx]; |
287 | |
288 | while (_data) { |
289 | // compare hash first |
290 | if (_data->hash == hash && _data->get_name() == p_static_string.ptr) { |
291 | break; |
292 | } |
293 | _data = _data->next; |
294 | } |
295 | |
296 | if (_data && _data->refcount.ref()) { |
297 | // exists |
298 | if (p_static) { |
299 | _data->static_count.increment(); |
300 | } |
301 | #ifdef DEBUG_ENABLED |
302 | if (unlikely(debug_stringname)) { |
303 | _data->debug_references++; |
304 | } |
305 | #endif |
306 | return; |
307 | } |
308 | |
309 | _data = memnew(_Data); |
310 | |
311 | _data->refcount.init(); |
312 | _data->static_count.set(p_static ? 1 : 0); |
313 | _data->hash = hash; |
314 | _data->idx = idx; |
315 | _data->cname = p_static_string.ptr; |
316 | _data->next = _table[idx]; |
317 | _data->prev = nullptr; |
318 | #ifdef DEBUG_ENABLED |
319 | if (unlikely(debug_stringname)) { |
320 | // Keep in memory, force static. |
321 | _data->refcount.ref(); |
322 | _data->static_count.increment(); |
323 | } |
324 | #endif |
325 | if (_table[idx]) { |
326 | _table[idx]->prev = _data; |
327 | } |
328 | _table[idx] = _data; |
329 | } |
330 | |
331 | StringName::StringName(const String &p_name, bool p_static) { |
332 | _data = nullptr; |
333 | |
334 | ERR_FAIL_COND(!configured); |
335 | |
336 | if (p_name.is_empty()) { |
337 | return; |
338 | } |
339 | |
340 | MutexLock lock(mutex); |
341 | |
342 | uint32_t hash = p_name.hash(); |
343 | uint32_t idx = hash & STRING_TABLE_MASK; |
344 | |
345 | _data = _table[idx]; |
346 | |
347 | while (_data) { |
348 | if (_data->hash == hash && _data->get_name() == p_name) { |
349 | break; |
350 | } |
351 | _data = _data->next; |
352 | } |
353 | |
354 | if (_data && _data->refcount.ref()) { |
355 | // exists |
356 | if (p_static) { |
357 | _data->static_count.increment(); |
358 | } |
359 | #ifdef DEBUG_ENABLED |
360 | if (unlikely(debug_stringname)) { |
361 | _data->debug_references++; |
362 | } |
363 | #endif |
364 | return; |
365 | } |
366 | |
367 | _data = memnew(_Data); |
368 | _data->name = p_name; |
369 | _data->refcount.init(); |
370 | _data->static_count.set(p_static ? 1 : 0); |
371 | _data->hash = hash; |
372 | _data->idx = idx; |
373 | _data->cname = nullptr; |
374 | _data->next = _table[idx]; |
375 | _data->prev = nullptr; |
376 | #ifdef DEBUG_ENABLED |
377 | if (unlikely(debug_stringname)) { |
378 | // Keep in memory, force static. |
379 | _data->refcount.ref(); |
380 | _data->static_count.increment(); |
381 | } |
382 | #endif |
383 | |
384 | if (_table[idx]) { |
385 | _table[idx]->prev = _data; |
386 | } |
387 | _table[idx] = _data; |
388 | } |
389 | |
390 | StringName StringName::search(const char *p_name) { |
391 | ERR_FAIL_COND_V(!configured, StringName()); |
392 | |
393 | ERR_FAIL_NULL_V(p_name, StringName()); |
394 | if (!p_name[0]) { |
395 | return StringName(); |
396 | } |
397 | |
398 | MutexLock lock(mutex); |
399 | |
400 | uint32_t hash = String::hash(p_name); |
401 | uint32_t idx = hash & STRING_TABLE_MASK; |
402 | |
403 | _Data *_data = _table[idx]; |
404 | |
405 | while (_data) { |
406 | // compare hash first |
407 | if (_data->hash == hash && _data->get_name() == p_name) { |
408 | break; |
409 | } |
410 | _data = _data->next; |
411 | } |
412 | |
413 | if (_data && _data->refcount.ref()) { |
414 | #ifdef DEBUG_ENABLED |
415 | if (unlikely(debug_stringname)) { |
416 | _data->debug_references++; |
417 | } |
418 | #endif |
419 | |
420 | return StringName(_data); |
421 | } |
422 | |
423 | return StringName(); //does not exist |
424 | } |
425 | |
426 | StringName StringName::search(const char32_t *p_name) { |
427 | ERR_FAIL_COND_V(!configured, StringName()); |
428 | |
429 | ERR_FAIL_NULL_V(p_name, StringName()); |
430 | if (!p_name[0]) { |
431 | return StringName(); |
432 | } |
433 | |
434 | MutexLock lock(mutex); |
435 | |
436 | uint32_t hash = String::hash(p_name); |
437 | |
438 | uint32_t idx = hash & STRING_TABLE_MASK; |
439 | |
440 | _Data *_data = _table[idx]; |
441 | |
442 | while (_data) { |
443 | // compare hash first |
444 | if (_data->hash == hash && _data->get_name() == p_name) { |
445 | break; |
446 | } |
447 | _data = _data->next; |
448 | } |
449 | |
450 | if (_data && _data->refcount.ref()) { |
451 | return StringName(_data); |
452 | } |
453 | |
454 | return StringName(); //does not exist |
455 | } |
456 | |
457 | StringName StringName::search(const String &p_name) { |
458 | ERR_FAIL_COND_V(p_name.is_empty(), StringName()); |
459 | |
460 | MutexLock lock(mutex); |
461 | |
462 | uint32_t hash = p_name.hash(); |
463 | |
464 | uint32_t idx = hash & STRING_TABLE_MASK; |
465 | |
466 | _Data *_data = _table[idx]; |
467 | |
468 | while (_data) { |
469 | // compare hash first |
470 | if (_data->hash == hash && p_name == _data->get_name()) { |
471 | break; |
472 | } |
473 | _data = _data->next; |
474 | } |
475 | |
476 | if (_data && _data->refcount.ref()) { |
477 | #ifdef DEBUG_ENABLED |
478 | if (unlikely(debug_stringname)) { |
479 | _data->debug_references++; |
480 | } |
481 | #endif |
482 | return StringName(_data); |
483 | } |
484 | |
485 | return StringName(); //does not exist |
486 | } |
487 | |
488 | bool operator==(const String &p_name, const StringName &p_string_name) { |
489 | return p_name == p_string_name.operator String(); |
490 | } |
491 | bool operator!=(const String &p_name, const StringName &p_string_name) { |
492 | return p_name != p_string_name.operator String(); |
493 | } |
494 | |
495 | bool operator==(const char *p_name, const StringName &p_string_name) { |
496 | return p_name == p_string_name.operator String(); |
497 | } |
498 | bool operator!=(const char *p_name, const StringName &p_string_name) { |
499 | return p_name != p_string_name.operator String(); |
500 | } |
501 | |