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
36StaticCString StaticCString::create(const char *p_ptr) {
37 StaticCString scs;
38 scs.ptr = p_ptr;
39 return scs;
40}
41
42StringName::_Data *StringName::_table[STRING_TABLE_LEN];
43
44StringName _scs_create(const char *p_chr, bool p_static) {
45 return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName());
46}
47
48bool StringName::configured = false;
49Mutex StringName::mutex;
50
51#ifdef DEBUG_ENABLED
52bool StringName::debug_stringname = false;
53#endif
54
55void 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
63void 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
121void 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
152bool 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
160bool 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
168bool StringName::operator!=(const String &p_name) const {
169 return !(operator==(p_name));
170}
171
172bool StringName::operator!=(const char *p_name) const {
173 return !(operator==(p_name));
174}
175
176bool 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
182void 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
194StringName::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
204void 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
212StringName::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
273StringName::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
331StringName::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
390StringName 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
426StringName 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
457StringName 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
488bool operator==(const String &p_name, const StringName &p_string_name) {
489 return p_name == p_string_name.operator String();
490}
491bool operator!=(const String &p_name, const StringName &p_string_name) {
492 return p_name != p_string_name.operator String();
493}
494
495bool operator==(const char *p_name, const StringName &p_string_name) {
496 return p_name == p_string_name.operator String();
497}
498bool operator!=(const char *p_name, const StringName &p_string_name) {
499 return p_name != p_string_name.operator String();
500}
501