1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#pragma once
4
5#include "Prerequisites/BsPrerequisitesUtil.h"
6#include "Threading/BsSpinLock.h"
7
8namespace bs
9{
10 /** @addtogroup String
11 * @{
12 */
13
14 /**
15 * A string identifier that provides very fast comparisons to other string identifiers. Significantly faster than
16 * comparing raw strings.
17 *
18 * @note
19 * Essentially a unique ID is generated for each string and then the ID is used for comparisons as if you were using
20 * an integer or an enum.
21 * @note
22 * Thread safe.
23 */
24 class BS_UTILITY_EXPORT StringID
25 {
26 static constexpr const int HASH_TABLE_SIZE = 4096;
27 static constexpr const int MAX_CHUNK_COUNT = 50;
28 static constexpr const int ELEMENTS_PER_CHUNK = 256;
29 static constexpr const int STRING_SIZE = 256;
30
31 /** Helper class that performs string actions on both null terminated character arrays and standard strings. */
32 template<class T>
33 class StringIDUtil
34 {
35 public:
36 static UINT32 size(T const& input) { return 0; }
37 static void copy(T const& input, char* dest) { }
38 static bool compare(T const& a, char* b) { return 0; }
39 };
40
41 /** Internal data that is shared by all instances for a specific string. */
42 struct InternalData
43 {
44 UINT32 id;
45 InternalData* next;
46 char chars[STRING_SIZE];
47 };
48
49 /** Performs initialization of static members as soon as the library is loaded. */
50 struct InitStatics
51 {
52 InitStatics();
53 };
54
55 public:
56 StringID() = default;
57
58 StringID(const char* name)
59 {
60 construct(name);
61 }
62
63 StringID(const String& name)
64 {
65 construct(name);
66 }
67
68 template<int N>
69 StringID(const char name[N])
70 {
71 construct((const char*)name);
72 }
73
74 /** Compare to string ids for equality. Uses fast integer comparison. */
75 bool operator== (const StringID& rhs) const
76 {
77 return mData == rhs.mData;
78 }
79
80 /** Compare to string ids for inequality. Uses fast integer comparison. */
81 bool operator!= (const StringID& rhs) const
82 {
83 return mData != rhs.mData;
84 }
85
86 /** Implicitly converts to a normal string. */
87 operator String() const { return String(mData->chars); }
88
89 /** Returns true if the string id has no value assigned. */
90 bool empty() const
91 {
92 return mData == nullptr;
93 }
94
95 /** Returns the null-terminated name of the string id. */
96 const char* c_str() const
97 {
98 if (mData == nullptr)
99 return "";
100
101 return mData->chars;
102 }
103
104 /** Returns the unique identifier of the string. */
105 UINT32 id() const { return mData ? mData->id : -1; }
106
107 static const StringID NONE;
108
109 private:
110 /**Constructs a StringID object in a way that works for pointers to character arrays and standard strings. */
111 template<class T>
112 void construct(T const& name);
113
114 /** Calculates a hash value for the provided null-terminated string. */
115 template<class T>
116 UINT32 calcHash(T const& input);
117
118 /**
119 * Allocates a new string entry and assigns it a unique ID. Optionally expands the chunks buffer if the new entry
120 * doesn't fit.
121 */
122 InternalData* allocEntry();
123
124 InternalData* mData = nullptr;
125
126 static volatile InitStatics mInitStatics;
127 static InternalData* mStringHashTable[HASH_TABLE_SIZE];
128 static InternalData* mChunks[MAX_CHUNK_COUNT];
129
130 static UINT32 mNextId;
131 static UINT32 mNumChunks;
132 static SpinLock mSync;
133 };
134
135 /** @cond SPECIALIZATIONS */
136
137 template<> struct RTTIPlainType <StringID>
138 {
139 enum { id = TID_StringID }; enum { hasDynamicSize = 1 };
140
141 static void toMemory(const StringID& data, char* memory)
142 {
143 UINT32 size = getDynamicSize(data);
144
145 UINT32 curSize = sizeof(UINT32);
146 memcpy(memory, &size, curSize);
147 memory += curSize;
148
149 bool isEmpty = data.empty();
150 memory = rttiWriteElem(isEmpty, memory);
151
152 if (!isEmpty)
153 {
154 UINT32 length = (UINT32)strlen(data.c_str());
155 memcpy(memory, data.c_str(), length * sizeof(char));
156 }
157 }
158
159 static UINT32 fromMemory(StringID& data, char* memory)
160 {
161 UINT32 size;
162 memcpy(&size, memory, sizeof(UINT32));
163 memory += sizeof(UINT32);
164
165 bool empty = false;
166 memory = rttiReadElem(empty, memory);
167
168 if (!empty)
169 {
170 UINT32 length = (size - sizeof(UINT32) - sizeof(bool)) / sizeof(char);
171
172 auto name = (char*)bs_stack_alloc(length + 1);
173 memcpy(name, memory, length);
174 name[length] = '\0';
175
176 data = StringID(name);
177 bs_stack_free(name);
178 }
179
180 return size;
181 }
182
183 static UINT32 getDynamicSize(const StringID& data)
184 {
185 UINT32 dataSize = sizeof(bool) + sizeof(UINT32);
186
187 bool isEmpty = data.empty();
188 if (!isEmpty)
189 {
190 UINT32 length = (UINT32)strlen(data.c_str());
191 dataSize += length * sizeof(char);
192 }
193
194 return (UINT32)dataSize;
195 }
196 };
197
198 /** @endcond */
199 /** @} */
200}
201
202/** @cond STDLIB */
203/** @addtogroup String
204 * @{
205 */
206
207namespace std
208{
209/** Hash value generator for StringID. */
210template<>
211struct hash<bs::StringID>
212{
213 size_t operator()(const bs::StringID& value) const
214 {
215 return (size_t)value.id();
216 }
217};
218}
219
220/** @} */
221/** @endcond */
222