1#pragma once
2
3#include <basis/seadRawPrint.h>
4#include <basis/seadTypes.h>
5#include <container/seadSafeArray.h>
6#include <prim/seadSafeString.h>
7#include <prim/seadScopedLock.h>
8#include <thread/seadCriticalSection.h>
9
10namespace sead
11{
12class EnumUtil
13{
14public:
15 static void parseText_(char** text_ptr, char* text_all, int size);
16
17 static CriticalSection* getParseTextCS_();
18 static CriticalSection* getInitValueArrayCS_();
19
20 static constexpr int countValues(const char* text_all, size_t text_all_len)
21 {
22 int count = 1;
23 for (size_t i = 0; i < text_all_len; ++i)
24 {
25 if (text_all[i] == ',')
26 ++count;
27 }
28 return count;
29 }
30
31private:
32 static void skipToWordEnd_(char** p_ptr, char** p_next);
33 static void skipToWordStart_(char** p_ptr);
34};
35} // namespace sead
36
37/// Define an enum class. Custom enumerator values are *not* supported.
38///
39/// Example:
40///
41/// SEAD_ENUM(CoreId, cMain, cSub1, cSub2)
42///
43#define SEAD_ENUM(NAME, ...) \
44 class NAME \
45 { \
46 /* Note: We cannot provide volatile overloads of the copy constructor and assignment \
47 * operator, because doing so would cause this class to stop being trivially copyable. \
48 * As a workaround we provide an implicit conversion to CvRef and a copy assignment \
49 * operator overload that takes a CvRef. The operator int() and NAME(int) constructor \
50 * allow construction from a const volatile NAME. */ \
51 struct CvRef \
52 { \
53 const NAME& asRef() const { return const_cast<const NAME&>(cvref); } \
54 \
55 bool is_cv; \
56 const volatile NAME& cvref; \
57 }; \
58 \
59 public: \
60 enum ValueType \
61 { \
62 __VA_ARGS__ \
63 }; \
64 \
65 /* This must be user-defined for correct codegen of static constructors. */ \
66 NAME() : mIdx(0) {} \
67 /* NOLINTNEXTLINE(google-explicit-constructor,cppcoreguidelines-pro-type-member-init) */ \
68 NAME(ValueType value) { setRelativeIndex(value); } \
69 /* NOLINTNEXTLINE(google-explicit-constructor,cppcoreguidelines-pro-type-member-init) */ \
70 NAME(int idx) { setRelativeIndex(idx); } \
71 NAME(const NAME& other) = default; \
72 \
73 NAME& operator=(const NAME& other) = default; \
74 NAME& operator=(ValueType value) \
75 { \
76 setRelativeIndex(value); \
77 return *this; \
78 } \
79 volatile NAME& operator=(ValueType value) volatile \
80 { \
81 setRelativeIndex(value); \
82 return *this; \
83 } \
84 volatile NAME& operator=(CvRef other) volatile \
85 { \
86 setRelativeIndex(other.is_cv ? other.cvref.mIdx : other.asRef().mIdx); \
87 return *this; \
88 } \
89 \
90 bool operator==(const NAME& rhs) const { return mIdx == rhs.mIdx; } \
91 bool operator!=(const NAME& rhs) const { return mIdx != rhs.mIdx; } \
92 \
93 bool operator==(ValueType value) const { return ValueType(mIdx) == value; } \
94 bool operator==(ValueType value) const volatile { return ValueType(mIdx) == value; } \
95 \
96 bool operator!=(ValueType value) const { return ValueType(mIdx) != value; } \
97 bool operator!=(ValueType value) const volatile { return ValueType(mIdx) != value; } \
98 \
99 ValueType value() const { return static_cast<ValueType>(mIdx); } \
100 ValueType value() const volatile { return static_cast<ValueType>(mIdx); } \
101 /* XXX: Bafflingly, there is no purely const-qualified version of operator int(). */ \
102 /* This leads to suboptimal codegen in many places. */ \
103 operator int() const volatile { return value(); } \
104 operator CvRef() const { return {false, *this}; } \
105 operator CvRef() const volatile { return {true, *this}; } \
106 \
107 bool fromText(const sead::SafeString& name) \
108 { \
109 for (int i = 0; i < size(); ++i) \
110 { \
111 if (name.isEqual(text(i))) \
112 { \
113 mIdx = i; \
114 return true; \
115 } \
116 } \
117 return false; \
118 } \
119 const char* text() const { return text(mIdx); } \
120 const char* text() const volatile { return text(mIdx); } \
121 static const char* text(int idx) { return text_(idx); } \
122 \
123 int getRelativeIndex() const { return mIdx; } \
124 int getRelativeIndex() const volatile { return mIdx; } \
125 void setRelativeIndex(int idx) \
126 { \
127 SEAD_ASSERT_MSG(u32(idx) < u32(size()), "range over: %d, [%d - %d)", idx, 0, size()); \
128 mIdx = idx; \
129 } \
130 void setRelativeIndex(int idx) volatile \
131 { \
132 SEAD_ASSERT_MSG(u32(idx) < u32(size()), "range over: %d, [%d - %d)", idx, 0, size()); \
133 mIdx = idx; \
134 } \
135 \
136 const char* getTypeText() const { return #NAME; } \
137 const char* getTypeText() const volatile { return #NAME; } \
138 constexpr static int size() { return cCount; } \
139 constexpr static int getSize() { return size(); } \
140 constexpr static int getLastIndex() { return size() - 1; } \
141 \
142 static void initialize() { text(0); } \
143 \
144 class iterator \
145 { \
146 public: \
147 explicit iterator(int idx) : mIdx(idx) {} \
148 bool operator==(const iterator& rhs) const { return mIdx == rhs.mIdx; } \
149 bool operator!=(const iterator& rhs) const { return mIdx != rhs.mIdx; } \
150 iterator& operator++() \
151 { \
152 if (mIdx <= getLastIndex()) \
153 { \
154 ++mIdx; \
155 } \
156 else \
157 { \
158 SEAD_ASSERT_MSG(false, "enum iterator overflow"); \
159 mIdx = size(); \
160 } \
161 return *this; \
162 } \
163 iterator& operator--() \
164 { \
165 --mIdx; \
166 return *this; \
167 } \
168 NAME operator*() const { return NAME(mIdx); } \
169 \
170 private: \
171 int mIdx; \
172 }; \
173 \
174 static iterator begin() { return iterator(0); } \
175 static iterator end() { return iterator(size()); } \
176 \
177 private: \
178 /* Returns nullptr when not found. */ \
179 static const char* text_(int idx) \
180 { \
181 if (u32(idx) >= cCount) \
182 return nullptr; \
183 \
184 static char** spTextPtr = nullptr; \
185 if (spTextPtr) \
186 return spTextPtr[idx]; \
187 { \
188 sead::ScopedLock<sead::CriticalSection> lock(sead::EnumUtil::getParseTextCS_()); \
189 if (!spTextPtr) \
190 { \
191 static char* sTextPtr[cCount]; \
192 static sead::FixedSafeString<cTextAllLen> sTextAll = \
193 sead::SafeString(cTextAll); \
194 sead::EnumUtil::parseText_(sTextPtr, sTextAll.getBuffer(), cCount); \
195 spTextPtr = sTextPtr; \
196 } \
197 } \
198 return spTextPtr[idx]; \
199 } \
200 \
201 static constexpr const char* cTextAll = #__VA_ARGS__; \
202 static constexpr size_t cTextAllLen = sizeof(#__VA_ARGS__); \
203 static constexpr int cCount = sead::EnumUtil::countValues(cTextAll, cTextAllLen); \
204 \
205 int mIdx; \
206 };
207
208/// Define a complex enum class with custom enumerator values with this macro.
209/// You must then use SEAD_ENUM_EX_VALUES and define the enum values **in the same order**.
210///
211/// Example:
212///
213/// SEAD_ENUM_EX(AssetType, Wave, Stream, Unknown)
214/// or SEAD_ENUM_EX(AssetType, Wave = 0, Stream = 1, Unknown = 0xFF)
215///
216/// followed by
217///
218/// SEAD_ENUM_EX_VALUES(AssetType, 0, 1, 0xFF)
219/// or SEAD_ENUM_EX_VALUES(AssetType, Wave, Stream, Unknown)
220///
221/// at namespace scope.
222///
223/// For the common case where enumerators do not require custom values, use SEAD_ENUM.
224///
225#define SEAD_ENUM_EX(NAME, ...) \
226 class NAME \
227 { \
228 public: \
229 enum ValueType \
230 { \
231 __VA_ARGS__ \
232 }; \
233 \
234 struct IndexTag \
235 { \
236 }; \
237 \
238 NAME() : mIdx(0) {} \
239 /* NOLINTNEXTLINE(google-explicit-constructor,cppcoreguidelines-pro-type-member-init) */ \
240 NAME(ValueType value) : NAME(static_cast<int>(value)) {} \
241 /* NOLINTNEXTLINE(google-explicit-constructor,cppcoreguidelines-pro-type-member-init) */ \
242 NAME(int value) \
243 { \
244 int idx = findRelativeIndex_(static_cast<ValueType>(value)); \
245 if (idx == -1) \
246 { \
247 SEAD_ASSERT_MSG(false, "could not convert to relative index: %d", idx); \
248 idx = 0; \
249 } \
250 setRelativeIndex(idx); \
251 } \
252 NAME(IndexTag, int index) : mIdx(index) {} \
253 \
254 NAME& operator=(const NAME& other) = default; \
255 bool operator==(const NAME& rhs) const { return mIdx == rhs.mIdx; } \
256 bool operator!=(const NAME& rhs) const { return mIdx != rhs.mIdx; } \
257 bool operator==(ValueType value) const { return mIdx == findRelativeIndex_(value); } \
258 bool operator!=(ValueType value) const { return mIdx != findRelativeIndex_(value); } \
259 \
260 int value() const { return getArray_()[getRelativeIndex()]; } \
261 int value() const volatile { return getArray_()[getRelativeIndex()]; } \
262 /* XXX: Bafflingly, there is no purely const-qualified version of operator int(). */ \
263 /* This leads to suboptimal codegen in many places. */ \
264 operator int() const volatile { return value(); } \
265 \
266 bool fromText(const sead::SafeString& name) \
267 { \
268 for (auto it = begin(), it_end = end(); it != it_end; ++it) \
269 { \
270 if (name == it.get().text()) \
271 { \
272 setRelativeIndex(it.getIndex()); \
273 return true; \
274 } \
275 } \
276 return false; \
277 } \
278 const char* text() const { return text(mIdx); } \
279 const char* text() const volatile { return text(mIdx); } \
280 static const char* text(int idx) { return text_(idx); } \
281 \
282 int getRelativeIndex() const { return mIdx; } \
283 int getRelativeIndex() const volatile { return mIdx; } \
284 void setRelativeIndex(int idx) \
285 { \
286 SEAD_ASSERT_MSG(u32(idx) < size(), "range over: %d, [%d - %d)", idx, 0, size()); \
287 mIdx = idx; \
288 } \
289 void setRelativeIndex(int idx) volatile \
290 { \
291 SEAD_ASSERT_MSG(u32(idx) < size(), "range over: %d, [%d - %d)", idx, 0, size()); \
292 mIdx = idx; \
293 } \
294 static int findRelativeIndex(ValueType value) { return findRelativeIndex_(value); } \
295 \
296 const char* getTypeText() const { return #NAME; } \
297 const char* getTypeText() const volatile { return #NAME; } \
298 constexpr static int size() { return cCount; } \
299 constexpr static int getSize() { return size(); } \
300 constexpr static int getLastIndex() { return size() - 1; } \
301 \
302 static void initialize() { text(0); } \
303 \
304 class iterator \
305 { \
306 public: \
307 explicit iterator(int idx) : mIdx(idx) {} \
308 bool operator==(const iterator& rhs) const { return mIdx == rhs.mIdx; } \
309 bool operator!=(const iterator& rhs) const { return mIdx != rhs.mIdx; } \
310 iterator& operator++() \
311 { \
312 if (mIdx > getLastIndex()) \
313 { \
314 SEAD_ASSERT_MSG(false, "enum iterator overflow"); \
315 mIdx = getLastIndex(); \
316 } \
317 else \
318 { \
319 ++mIdx; \
320 } \
321 return *this; \
322 } \
323 iterator& operator--() \
324 { \
325 --mIdx; \
326 return *this; \
327 } \
328 NAME operator*() const { return get(); } \
329 NAME get() const { return NAME(IndexTag{}, mIdx); } \
330 int getIndex() const { return mIdx; } \
331 \
332 private: \
333 int mIdx; \
334 }; \
335 \
336 static iterator begin() { return iterator(0); } \
337 static iterator end() { return iterator(getArray_().size()); } \
338 \
339 private: \
340 class ValueArray \
341 { \
342 public: \
343 ValueArray(); \
344 \
345 s32 size() const { return mSize; } \
346 \
347 int& operator[](s32 idx) { return *get(idx); } \
348 const int& operator[](s32 idx) const { return *get(idx); } \
349 \
350 int* get(s32 idx) \
351 { \
352 if (u32(mSize) <= u32(idx)) \
353 { \
354 SEAD_ASSERT_MSG(false, "index exceeded [%d/%d]", idx, mSize); \
355 return mBuffer; \
356 } \
357 return &mBuffer[idx]; \
358 } \
359 \
360 const int* get(s32 idx) const \
361 { \
362 if (u32(mSize) <= u32(idx)) \
363 { \
364 SEAD_ASSERT_MSG(false, "index exceeded [%d/%d]", idx, mSize); \
365 return mBuffer; \
366 } \
367 return &mBuffer[idx]; \
368 } \
369 \
370 private: \
371 s32 mSize = 0; \
372 int* mBuffer = nullptr; \
373 }; \
374 friend class ValueArray; \
375 \
376 static ValueArray& getArray_() \
377 { \
378 static ValueArray sBuffer; \
379 return sBuffer; \
380 } \
381 \
382 /* Returns nullptr when not found. */ \
383 static const char* text_(int idx) \
384 { \
385 if (u32(idx) >= cCount) \
386 return nullptr; \
387 \
388 static char** spTextPtr = nullptr; \
389 if (spTextPtr) \
390 return spTextPtr[idx]; \
391 { \
392 sead::ScopedLock<sead::CriticalSection> lock(sead::EnumUtil::getParseTextCS_()); \
393 if (!spTextPtr) \
394 { \
395 static char* sTextPtr[cCount]; \
396 static sead::FixedSafeString<cTextAllLen> sTextAll = \
397 sead::SafeString(cTextAll); \
398 sead::EnumUtil::parseText_(sTextPtr, sTextAll.getBuffer(), cCount); \
399 spTextPtr = sTextPtr; \
400 } \
401 } \
402 return spTextPtr[idx]; \
403 } \
404 \
405 /* Returns -1 when not found. */ \
406 static int findRelativeIndex_(ValueType value) \
407 { \
408 const ValueArray& array = getArray_(); \
409 for (int i = 0, n = array.size(); i < n; ++i) \
410 { \
411 if (array[i] == value) \
412 return i; \
413 } \
414 return -1; \
415 } \
416 \
417 static constexpr const char* cTextAll = #__VA_ARGS__; \
418 static constexpr size_t cTextAllLen = sizeof(#__VA_ARGS__); \
419 static constexpr int cCount = sead::EnumUtil::countValues(cTextAll, cTextAllLen); \
420 \
421 int mIdx; \
422 };
423
424/// For use with SEAD_ENUM_EX.
425#define SEAD_ENUM_EX_VALUES(NAME, ...) \
426 NAME::ValueArray::ValueArray() \
427 { \
428 sead::ScopedLock<sead::CriticalSection> lock(sead::EnumUtil::getInitValueArrayCS_()); \
429 if (mBuffer) \
430 return; \
431 struct Array : sead::SafeArray<int, NAME::cCount> \
432 { \
433 Array() : sead::SafeArray<int, NAME::cCount>{__VA_ARGS__} {} \
434 }; \
435 static Array sArray{}; \
436 mSize = sArray.size(); \
437 mBuffer = sArray.getBufferPtr(); \
438 }
439