SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
DMX.cpp
Go to the documentation of this file.
1// ReSharper disable CppRedundantQualifier
2
3#include <kvpp/DMX.h>
4
5#include <format>
6#include <limits>
7#include <random>
8#include <sstream>
9#include <utility>
10
11#include <BufferStream.h>
12#include <iomanip>
13#include <sourcepp/FS.h>
14
15using namespace kvpp;
16using namespace sourcepp;
17
18// NOLINTNEXTLINE(*-no-recursion)
19std::string DMXValue::idToString(ID id) {
20 switch (id) {
21 case ID::INVALID: break;
22 case ID::ELEMENT: return "element";
23 case ID::INT32: return "int";
24 case ID::FLOAT: return "float";
25 case ID::BOOL: return "bool";
26 case ID::STRING: return "string";
27 case ID::BYTEARRAY: return "binary";
28 case ID::UUID: return "elementid";
29 case ID::TIME: return "time";
30 case ID::COLOR: return "color";
31 case ID::VECTOR2: return "vector2";
32 case ID::VECTOR3: return "vector3";
33 case ID::VECTOR4: return "vector4";
34 case ID::EULER_ANGLES: return "angle";
35 case ID::QUATERNION: return "quaternion";
36 case ID::MATRIX_4X4: return "matrix";
37 case ID::UINT64: return "uint64";
38 case ID::UINT8: return "uint8";
40 case ID::ARRAY_INT32:
41 case ID::ARRAY_FLOAT:
42 case ID::ARRAY_BOOL:
45 case ID::ARRAY_UUID:
46 case ID::ARRAY_TIME:
47 case ID::ARRAY_COLOR:
55 case ID::ARRAY_UINT8:
56 return idToString(arrayIDToInnerID(id)) + "_array";
57 }
58 return "invalid";
59}
60
62 return static_cast<DMXValue::ID>(this->value.index()) == DMXValue::ID::INVALID;
63}
64
65DMXAttribute::operator bool() const {
66 return !this->isInvalid();
67}
68
69std::string_view DMXAttribute::getKey() const {
70 return this->key;
71}
72
73void DMXAttribute::setKey(std::string key_) {
74 this->key = std::move(key_);
75}
76
78 return static_cast<DMXValue::ID>(this->value.index());
79}
80
83}
84
86 return this->value;
87}
88
89std::string DMXAttribute::getValueString() const {
90 switch (const auto type = this->getValueType()) {
91 using enum DMXValue::ID;
92 case INVALID:
94 case ELEMENT: {
95 const auto [index, stubGUID] = this->getValue<DMXValue::Element>();
96 return std::format("{}{}", index == -2 ? "GUID: " : "#", index);
97 }
98 case INT32:
99 return std::format("{}", this->getValue<int32_t>());
100 case FLOAT:
101 return std::format("{}", this->getValue<float>());
102 case BOOL:
103 return std::format("{}", static_cast<int>(this->getValue<bool>()));
104 case STRING:
105 return this->getValue<std::string>();
106 case BYTEARRAY: {
107 std::stringstream hex;
108 hex << std::hex << std::setfill('0');
109 for (auto byte : this->getValue<DMXValue::ByteArray>()) {
110 hex << std::setw(2) << static_cast<unsigned char>(byte);
111 }
112 return hex.str();
113 }
114 case UUID: {
115 std::stringstream hex;
116 hex << std::hex << std::setfill('0');
117 for (auto byte : this->getValue<DMXValue::UUID>()) {
118 hex << std::setw(2) << static_cast<unsigned char>(byte);
119 }
120 return hex.str();
121 }
122 case TIME:
123 return std::format("{}", this->getValue<DMXValue::Time>().seconds);
124 case COLOR: {
125 const auto [r, g, b, a] = this->getValue<DMXValue::Color>();
126 return std::format("rgba({}, {}, {}, {})", r, g, b, a);
127 }
128 case VECTOR2: {
129 const auto vec2 = this->getValue<DMXValue::Vector2>();
130 return std::format("[{}, {}]", vec2[0], vec2[1]);
131 }
132 case VECTOR3: {
133 const auto vec3 = this->getValue<DMXValue::Vector3>();
134 return std::format("[{}, {}, {}]", vec3[0], vec3[1], vec3[2]);
135 }
136 case VECTOR4: {
137 const auto vec4 = this->getValue<DMXValue::Vector4>();
138 return std::format("[{}, {}, {}, {}]", vec4[0], vec4[1], vec4[2], vec4[3]);
139 }
140 case EULER_ANGLES: {
141 const auto angles = this->getValue<DMXValue::EulerAngles>();
142 return std::format("[{}, {}, {}]", angles[0], angles[1], angles[2]);
143 }
144 case QUATERNION: {
145 const auto quat = this->getValue<DMXValue::Quaternion>();
146 return std::format("[{}, {}, {}, {}]", quat[0], quat[1], quat[2], quat[3]);
147 }
148 case MATRIX_4X4: {
149 const auto mat4 = this->getValue<DMXValue::Matrix4x4>();
150 std::string out;
151 for (int i = 0; i < 4; i++) {
152 out += i == 0 ? '[' : ' ';
153 for (int j = 0; j < 4; j++) {
154 out += std::format("{}{}", mat4[i][j], j < 3 ? ", " : i < 3 ? ",\n" : "]");
155 }
156 }
157 return out;
158 }
159 case UINT64:
160 return std::format("{}", this->getValue<uint64_t>());
161 case UINT8:
162 return std::format("{}", static_cast<int>(this->getValue<uint8_t>()));
163 case ARRAY_ELEMENT: {
164 const auto elements = this->getValue<std::vector<DMXValue::Element>>();
165 std::string out = "[";
166 for (int i = 0; i < elements.size(); i++) {
167 if (elements[i].index == -2) {
168 out += std::format("{}GUID: {}{}", i == 0 ? "" : " ", elements[i].externalGUID, i == elements.size() - 1 ? "" : ",");
169 } else {
170 out += std::format("{}#{}{}", i == 0 ? "" : " ", elements[i].index, i == elements.size() - 1 ? "" : ",");
171 }
172 }
173 return out + ']';
174 }
175 case ARRAY_INT32: {
176 const auto ints = this->getValue<std::vector<int32_t>>();
177 std::string out = "[";
178 for (int i = 0; i < ints.size(); i++) {
179 out += std::format("{}{}{}", i == 0 ? "" : " ", ints[i], i == ints.size() - 1 ? "" : ",");
180 }
181 return out + ']';
182 }
183 case ARRAY_FLOAT: {
184 const auto floats = this->getValue<std::vector<float>>();
185 std::string out = "[";
186 for (int i = 0; i < floats.size(); i++) {
187 out += std::format("{}{}{}", i == 0 ? "" : " ", floats[i], i == floats.size() - 1 ? "" : ",");
188 }
189 return out + ']';
190 }
191 case ARRAY_BOOL: {
192 const auto bools = this->getValue<std::vector<bool>>();
193 std::string out = "[";
194 for (int i = 0; i < bools.size(); i++) {
195 // ReSharper disable once CppRedundantCastExpression
196 out += std::format("{}{}{}", i == 0 ? "" : " ", static_cast<bool>(bools[i]), i == bools.size() - 1 ? "" : ",");
197 }
198 return out + ']';
199 }
200 case ARRAY_STRING: {
201 const auto strings = this->getValue<std::vector<std::string>>();
202 std::string out = "[";
203 for (int i = 0; i < strings.size(); i++) {
204 out += std::format("{}{}{}", i == 0 ? "" : " ", strings[i], i == strings.size() - 1 ? "" : ",\n");
205 }
206 return out + ']';
207 }
208 case ARRAY_BYTEARRAY: {
209 const auto bytearrays = this->getValue<std::vector<DMXValue::ByteArray>>();
210 std::string out = "[";
211 for (int i = 0; i < bytearrays.size(); i++) {
212 std::stringstream hex;
213 hex << std::hex << std::setfill('0');
214 for (auto byte : bytearrays[i]) {
215 hex << std::setw(2) << static_cast<unsigned char>(byte);
216 }
217 out += std::format("{}{}{}", i == 0 ? "" : " ", hex.str(), i == bytearrays.size() - 1 ? "" : ",\n");
218 }
219 return out + ']';
220 }
221 case ARRAY_UUID: {
222 const auto uuids = this->getValue<std::vector<DMXValue::UUID>>();
223 std::string out = "[";
224 for (int i = 0; i < uuids.size(); i++) {
225 std::stringstream hex;
226 hex << std::hex << std::setfill('0');
227 for (auto byte : uuids[i]) {
228 hex << std::setw(2) << static_cast<unsigned char>(byte);
229 }
230 out += std::format("{}{}{}", i == 0 ? "" : " ", hex.str(), i == uuids.size() - 1 ? "" : ",\n");
231 }
232 return out + ']';
233 }
234 case ARRAY_TIME: {
235 const auto times = this->getValue<std::vector<DMXValue::Time>>();
236 std::string out = "[";
237 for (int i = 0; i < times.size(); i++) {
238 out += std::format("{}{}{}", i == 0 ? "" : " ", times[i].seconds, i == times.size() - 1 ? "" : ",");
239 }
240 return out + ']';
241 }
242 case ARRAY_COLOR: {
243 const auto colors = this->getValue<std::vector<DMXValue::Color>>();
244 std::string out = "[";
245 for (int i = 0; i < colors.size(); i++) {
246 out += std::format("{}rgba({}, {}, {}, {}){}", i == 0 ? "" : " ", colors[i].r, colors[i].g, colors[i].b, colors[i].a, i == colors.size() - 1 ? "" : ",\n");
247 }
248 return out + ']';
249 }
250 case ARRAY_VECTOR2: {
251 const auto vecs = this->getValue<std::vector<DMXValue::Vector2>>();
252 std::string out = "[";
253 for (int i = 0; i < vecs.size(); i++) {
254 out += std::format("{}[{}, {}]{}", i == 0 ? "" : " ", vecs[i][0], vecs[i][1], i == vecs.size() - 1 ? "" : ",\n");
255 }
256 return out + ']';
257 }
258 case ARRAY_VECTOR3: {
259 const auto vecs = this->getValue<std::vector<DMXValue::Vector3>>();
260 std::string out = "[";
261 for (int i = 0; i < vecs.size(); i++) {
262 out += std::format("{}[{}, {}, {}]{}", i == 0 ? "" : " ", vecs[i][0], vecs[i][1], vecs[i][2], i == vecs.size() - 1 ? "" : ",\n");
263 }
264 return out + ']';
265 }
266 case ARRAY_VECTOR4: {
267 const auto vecs = this->getValue<std::vector<DMXValue::Vector4>>();
268 std::string out = "[";
269 for (int i = 0; i < vecs.size(); i++) {
270 out += std::format("{}[{}, {}, {}, {}]{}", i == 0 ? "" : " ", vecs[i][0], vecs[i][1], vecs[i][2], vecs[i][3], i == vecs.size() - 1 ? "" : ",\n");
271 }
272 return out + ']';
273 }
274 case ARRAY_EULER_ANGLES: {
275 const auto angles = this->getValue<std::vector<DMXValue::EulerAngles>>();
276 std::string out = "[";
277 for (int i = 0; i < angles.size(); i++) {
278 out += std::format("{}[{}, {}, {}]{}", i == 0 ? "" : " ", angles[i][0], angles[i][1], angles[i][2], i == angles.size() - 1 ? "" : ",\n");
279 }
280 return out + ']';
281 }
282 case ARRAY_QUATERNION: {
283 const auto quats = this->getValue<std::vector<DMXValue::Quaternion>>();
284 std::string out = "[";
285 for (int i = 0; i < quats.size(); i++) {
286 out += std::format("{}[{}, {}, {}, {}]{}", i == 0 ? "" : " ", quats[i][0], quats[i][1], quats[i][2], quats[i][3], i == quats.size() - 1 ? "" : ",\n");
287 }
288 return out + ']';
289 }
290 case ARRAY_MATRIX_4X4: {
291 const auto matrices = this->getValue<std::vector<DMXValue::Matrix4x4>>();
292 std::string out = "[";
293 for (int m = 0; m < matrices.size(); m++) {
294 out += m == 0 ? "[" : " [";
295 for (int i = 0; i < 4; i++) {
296 out += i == 0 ? "" : " ";
297 for (int j = 0; j < 4; j++) {
298 out += std::format("{}{}", matrices[m][i][j], j < 3 ? ", " : i < 3 ? ",\n" : "]");
299 }
300 }
301 if (m < matrices.size() - 1) {
302 out += ",\n";
303 }
304 }
305 return out + ']';
306 }
307 case ARRAY_UINT64: {
308 const auto ints = this->getValue<std::vector<uint64_t>>();
309 std::string out = "[";
310 for (int i = 0; i < ints.size(); i++) {
311 out += std::format("{}{}{}", i == 0 ? "" : " ", ints[i], i == ints.size() - 1 ? "" : ",");
312 }
313 return out + ']';
314 }
315 case ARRAY_UINT8: {
316 const auto ints = this->getValue<std::vector<uint8_t>>();
317 std::string out = "[";
318 for (int i = 0; i < ints.size(); i++) {
319 out += std::format("{}{}{}", i == 0 ? "" : " ", ints[i], i == ints.size() - 1 ? "" : ",");
320 }
321 return out + ']';
322 }
323 }
324 return "";
325}
326
328 this->value = std::move(value_);
329}
330
332 this->value = std::move(value_);
333 return *this;
334}
335
336DMXElement::operator bool() const {
337 return !this->type.empty() || !this->key.empty() || this->uuid != DMXValue::UUID{};
338}
339
340std::string_view DMXElement::getType() const {
341 return this->type;
342}
343
344void DMXElement::setType(std::string type_) {
345 this->type = std::move(type_);
346}
347
348std::string_view DMXElement::getKey() const {
349 return this->key;
350}
351
352void DMXElement::setKey(std::string key_) {
353 this->key = std::move(key_);
354}
355
357 return this->uuid;
358}
359
361 this->uuid = guid_;
362}
363
364bool DMXElement::hasAttribute(std::string_view attributeKey) const {
365 return !this->operator[](attributeKey);
366}
367
369 DMXAttribute attr;
370 attr.setKey(std::move(key_));
371 attr.setValue(std::move(value_));
372 this->attributes.push_back(std::move(attr));
373 return this->attributes.back();
374}
375
377 return this->attributes.size();
378}
379
380uint64_t DMXElement::getAttributeCount(std::string_view childKey) const {
381 uint64_t count = 0;
382 for (const DMXAttribute& element : this->attributes) {
383 if (string::iequals(element.key, childKey)) {
384 ++count;
385 }
386 }
387 return count;
388}
389
390const std::vector<DMXAttribute>& DMXElement::getAttributes() const {
391 return this->attributes;
392}
393
394std::vector<DMXAttribute>& DMXElement::getAttributes() {
395 return this->attributes;
396}
397
398const DMXAttribute& DMXElement::operator[](unsigned int n) const {
399 return this->attributes.at(n);
400}
401
403 return this->attributes.at(n);
404}
405
406const DMXAttribute& DMXElement::operator[](std::string_view attributeKey) const {
407 return this->operator()(attributeKey);
408}
409
410DMXAttribute& DMXElement::operator[](std::string_view attributeKey) {
411 return this->operator()(attributeKey);
412}
413
414const DMXAttribute& DMXElement::operator()(std::string_view attributeKey) const {
415 for (const auto& attribute : this->attributes) {
416 if (string::iequals(attribute.getKey(), attributeKey)) {
417 return attribute;
418 }
419 }
420 return getInvalidAttribute();
421}
422
423DMXAttribute& DMXElement::operator()(std::string_view attributeKey) {
424 for (auto& attribute : this->attributes) {
425 if (string::iequals(attribute.getKey(), attributeKey)) {
426 return attribute;
427 }
428 }
429 return this->addAttribute(std::string{attributeKey});
430}
431
432const DMXAttribute& DMXElement::operator()(std::string_view attributeKey, unsigned int n) const {
433 unsigned int count = 0;
434 for (const auto& attribute : this->attributes) {
435 if (string::iequals(attribute.getKey(), attributeKey)) {
436 if (count == n) {
437 return attribute;
438 }
439 if (++count > n) {
440 break;
441 }
442 }
443 }
444 return getInvalidAttribute();
445}
446
447DMXAttribute& DMXElement::operator()(std::string_view attributeKey, unsigned int n) {
448 unsigned int count = 0;
449 for (auto& attribute : this->attributes) {
450 if (string::iequals(attribute.getKey(), attributeKey)) {
451 if (count == n) {
452 return attribute;
453 }
454 if (++count > n) {
455 break;
456 }
457 }
458 }
459 return this->addAttribute(std::string{attributeKey});
460}
461
462void DMXElement::removeAttribute(unsigned int n) {
463 if (this->attributes.size() > n) {
464 this->attributes.erase(this->attributes.begin() + n);
465 }
466}
467
468void DMXElement::removeAttribute(std::string_view attributeKey, int n) {
469 unsigned int count = 0;
470 for (auto attribute = this->attributes.begin(); attribute != this->attributes.end(); ++attribute) {
471 if (string::iequals(attribute->getKey(), attributeKey)) {
472 if (n < 0 || count == n) {
473 attribute = this->attributes.erase(attribute);
474 if (count == n) {
475 return;
476 }
477 }
478 ++count;
479 }
480 }
481}
482
484 static DMXAttribute attribute;
485 return attribute;
486}
487
488DMX::DMX(Encoding encodingType_, int encodingVersion_, std::string formatType_, int formatVersion_)
489 : encodingType{encodingType_}
490 , encodingVersion{encodingVersion_}
491 , formatType{std::move(formatType_)}
492 , formatVersion{formatVersion_} {}
493
494DMX::DMX(std::span<const std::byte> dmxData) {
495 BufferStreamReadOnly stream{dmxData};
496
497 // Header can be at most MAX_HEADER characters long followed by either a null terminator or newline
498 static constexpr int MAX_FORMAT_LENGTH = 64;
499 static constexpr int MAX_HEADER_LENGTH = 40 + MAX_FORMAT_LENGTH * 2;
500
501 std::string header;
502 while (header.length() < MAX_HEADER_LENGTH) {
503 const char temp = stream.read<char>();
504 if (temp == '\0' || temp == '\n') {
505 break;
506 }
507 header += temp;
508 }
509
510 std::array<char, MAX_FORMAT_LENGTH> encodingTypeData{};
511 std::array<char, MAX_FORMAT_LENGTH> formatTypeData{};
512
513 static constexpr std::string_view HEADER_FORMAT = "<!-- dmx encoding %63s %i format %63s %i -->";
514 static constexpr std::string_view HEADER_FORMAT_OLD = "<!-- DMXVersion %63s_v%i -->";
515
516#ifdef _WIN32
517 bool headerParsed = sscanf_s(header.c_str(), HEADER_FORMAT.data(), encodingTypeData.data(), MAX_FORMAT_LENGTH, &this->encodingVersion, formatTypeData.data(), MAX_FORMAT_LENGTH, &this->formatVersion);
518#else
519 bool headerParsed = std::sscanf(header.c_str(), HEADER_FORMAT.data(), encodingTypeData.data(), &this->encodingVersion, formatTypeData.data(), &this->formatVersion); // NOLINT(*-err34-c)
520#endif
521 if (!headerParsed) {
522 // Try old header, then bail if it's genuinely malformed
523#ifdef _WIN32
524 headerParsed = sscanf_s(header.c_str(), HEADER_FORMAT_OLD.data(), encodingTypeData.data(), MAX_FORMAT_LENGTH, &this->encodingVersion);
525#else
526 headerParsed = std::sscanf(header.c_str(), HEADER_FORMAT_OLD.data(), encodingTypeData.data(), &this->encodingVersion); // NOLINT(*-err34-c)
527#endif
528 if (!headerParsed) {
530 return;
531 }
532
533 this->formatType = "dmx";
534
535 std::string_view encodingTypeStr = encodingTypeData.data();
536 if (encodingTypeStr.starts_with("unicode_")) {
537 this->encodingTypeHasUnicodePrefix = true;
538 encodingTypeStr = encodingTypeStr.substr(8);
539 }
540
541 if (encodingTypeStr == "binary") {
543 } else if (encodingTypeStr == "sfm") {
545 this->formatType = encodingTypeStr;
547 } else if (encodingTypeStr == "keyvalues2") {
549 } else if (encodingTypeStr == "keyvalues2_flat") {
551 } else {
553 return;
554 }
555 } else {
556 this->formatType = formatTypeData.data();
557
558 std::string_view encodingTypeStr = encodingTypeData.data();
559 if (encodingTypeStr.starts_with("unicode_")) {
560 this->encodingTypeHasUnicodePrefix = true;
561 encodingTypeStr = encodingTypeStr.substr(8);
562 }
563
564 if (encodingTypeStr == "binary") {
566 } else if (encodingTypeStr == "binary_seqids") {
568 } else if (encodingTypeStr == "keyvalues2") {
570 } else if (encodingTypeStr == "keyvalues2_flat") {
572 } else if (encodingTypeStr == "keyvalues2_noids") {
574 } else {
576 return;
577 }
578 }
579
582 return;
583 }
584
585 const auto readBinary = [this, &stream] {
586 // Version-specific conditionals
587 const bool isLegacyEncoding = this->encodingType == ENCODING_BINARY_OLD || this->encodingType == ENCODING_BINARY_OLD_SFM;
588 const DMXValue::IDVersion attributeIDVersion = isLegacyEncoding || this->encodingVersion < 3 ? DMXValue::IDVersion::V1 : this->encodingVersion < 9 ? DMXValue::IDVersion::V2 : DMXValue::IDVersion::V3;
589 const bool stringListExists = !isLegacyEncoding && this->encodingVersion > 1;
590 const bool elementNamesAndStringValuesAreStoredInStringList = !isLegacyEncoding && this->encodingVersion > 3;
591 const bool stringListLengthIsShort = this->encodingVersion < 4;
592 const bool stringListIndicesAreShort = this->encodingVersion < 5;
593 const bool preloadAttributeListExists = !isLegacyEncoding && this->encodingVersion > 5;
594
595 // Eat the null terminator for the header.
596 if (stream.read<char>() != 0) {
597 return false;
598 }
599
600 // Helper to read a string index and get the string from the list
601 std::vector<std::string> stringList;
602 const auto readStringFromIndex = [stringListExists, stringListIndicesAreShort, &stringList](BufferStream& stream_) -> std::string {
603 if (!stringListExists) {
604 return stream_.read_string();
605 }
606 uint32_t index;
607 if (stringListIndicesAreShort) {
608 index = stream_.read<uint16_t>();
609 } else {
610 index = stream_.read<uint32_t>();
611 }
612 if (index >= stringList.size()) {
613 // This is an intentional feature of the format
614 return "";
615 }
616 return stringList.at(index);
617 };
618
619 // Helper to read a value for an attribute
620 std::function<DMXValue::Generic(DMXValue::ID, bool)> readValue;
621 readValue = [&stream, &readStringFromIndex, &readValue](DMXValue::ID type, bool useStringList) -> DMXValue::Generic {
622 const auto readArrayValue = [&stream, &readValue]<typename T>(DMXValue::ID type_) {
623 std::vector<T> out;
624 auto size = stream.read<uint32_t>();
625 out.reserve(size);
626 for (int i = 0; i < size; i++) {
627 // String arrays are always inline
628 out.push_back(std::get<T>(readValue(DMXValue::arrayIDToInnerID(type_), false)));
629 }
630 return out;
631 };
632 switch (type) {
633 using enum DMXValue::ID;
634 case INVALID:
635 return std::monostate{};
636 case ELEMENT: {
637 DMXValue::Element value;
638 value.index = stream.read<int32_t>();
639 if (value.index == -2) {
640 // Read in the ASCII GUID
641 value.externalGUID = stream.read_string();
642 }
643 return value;
644 }
645 case INT32:
646 return stream.read<int32_t>();
647 case FLOAT:
648 return stream.read<float>();
649 case BOOL:
650 return stream.read<bool>();
651 case STRING:
652 return useStringList ? readStringFromIndex(stream) : stream.read_string();
653 case BYTEARRAY:
654 return stream.read_bytes(stream.read<uint32_t>());
655 case UUID:
656 return stream.read_bytes<16>();
657 case TIME:
658 return DMXValue::Time{static_cast<float>(static_cast<double>(stream.read<int32_t>()) / 10000.0)};
659 case COLOR: {
660 DMXValue::Color value{};
661 stream >> value.r >> value.g >> value.b >> value.a;
662 return value;
663 }
664 case VECTOR2: {
665 DMXValue::Vector2 value;
666 stream >> value[0] >> value[1];
667 return value;
668 }
669 case VECTOR3: {
670 DMXValue::Vector3 value;
671 stream >> value[0] >> value[1] >> value[2];
672 return value;
673 }
674 case VECTOR4: {
675 DMXValue::Vector4 value;
676 stream >> value[0] >> value[1] >> value[2] >> value[3];
677 return value;
678 }
679 case EULER_ANGLES: {
681 stream >> value[0] >> value[1] >> value[2];
682 return value;
683 }
684 case QUATERNION: {
686 stream >> value[0] >> value[1] >> value[2] >> value[3];
687 return value;
688 }
689 case MATRIX_4X4: {
691 stream
692 >> value[0][0] >> value[0][1] >> value[0][2] >> value[0][3]
693 >> value[1][0] >> value[1][1] >> value[1][2] >> value[1][3]
694 >> value[2][0] >> value[2][1] >> value[2][2] >> value[2][3]
695 >> value[3][0] >> value[3][1] >> value[3][2] >> value[3][3];
696 return value;
697 }
698 case UINT64:
699 return stream.read<uint64_t>();
700 case UINT8:
701 return stream.read<uint8_t>();
702 case ARRAY_ELEMENT:
703 return readArrayValue.operator()<DMXValue::Element>(type);
704 case ARRAY_INT32:
705 return readArrayValue.operator()<int32_t>(type);
706 case ARRAY_FLOAT:
707 return readArrayValue.operator()<float>(type);
708 case ARRAY_BOOL:
709 return readArrayValue.operator()<bool>(type);
710 case ARRAY_STRING:
711 return readArrayValue.operator()<std::string>(type);
712 case ARRAY_BYTEARRAY:
713 return readArrayValue.operator()<std::vector<std::byte>>(type);
714 case ARRAY_UUID:
715 return readArrayValue.operator()<DMXValue::UUID>(type);
716 case ARRAY_TIME:
717 return readArrayValue.operator()<DMXValue::Time>(type);
718 case ARRAY_COLOR:
719 return readArrayValue.operator()<DMXValue::Color>(type);
720 case ARRAY_VECTOR2:
721 return readArrayValue.operator()<DMXValue::Vector2>(type);
722 case ARRAY_VECTOR3:
723 return readArrayValue.operator()<DMXValue::Vector3>(type);
724 case ARRAY_VECTOR4:
725 return readArrayValue.operator()<DMXValue::Vector4>(type);
726 case ARRAY_EULER_ANGLES:
727 return readArrayValue.operator()<DMXValue::EulerAngles>(type);
728 case ARRAY_QUATERNION:
729 return readArrayValue.operator()<DMXValue::Quaternion>(type);
730 case ARRAY_MATRIX_4X4:
731 return readArrayValue.operator()<DMXValue::Matrix4x4>(type);
732 case ARRAY_UINT64:
733 return readArrayValue.operator()<uint64_t>(type);
734 case ARRAY_UINT8:
735 return readArrayValue.operator()<uint8_t>(type);
736 }
737 return std::monostate{};
738 };
739
740 // Helper to read element attributes
741 const auto readAttributes = [&stream, attributeIDVersion, &readStringFromIndex, &readValue](DMXElement& element, bool useStringList) {
742 const auto attributeCount = stream.read<int32_t>();
743 element.attributes.reserve(attributeCount);
744
745 for (int i = 0; i < attributeCount; i++) {
746 element.attributes.push_back(DMXAttribute{});
747 auto& attribute = element.attributes.back();
748
749 attribute.setKey(readStringFromIndex(stream));
750
751 auto attributeID = DMXValue::ID::INVALID;
752 switch (attributeIDVersion) {
753 // ReSharper disable once CppDFAUnreachableCode
754 case DMXValue::IDVersion::V1: attributeID = DMXValue::decodeID(static_cast<DMXValue::IDv1>(stream.read<std::byte>())); break;
755 case DMXValue::IDVersion::V2: attributeID = DMXValue::decodeID(static_cast<DMXValue::IDv2>(stream.read<std::byte>())); break;
756 case DMXValue::IDVersion::V3: attributeID = DMXValue::decodeID(static_cast<DMXValue::IDv3>(stream.read<std::byte>())); break;
757 }
758 attribute.setValue(readValue(attributeID, useStringList));
759 }
760 };
761
762 // Preload attributes
763 if (preloadAttributeListExists) {
764 if (const auto preloadElementCount = stream.read<uint32_t>()) {
765 this->prefixAttributeContainers.reserve(preloadElementCount);
766 for (uint32_t i = 0; i < preloadElementCount; i++) {
767 auto& element = this->addPrefixAttributeContainer();
768 readAttributes(element, false);
769 }
770 }
771 }
772
773 // String list
774 if (stringListExists) {
775 uint32_t stringCount;
776 if (stringListLengthIsShort) {
777 stringCount = stream.read<uint16_t>();
778 } else {
779 stringCount = stream.read<uint32_t>();
780 }
781 stringList.reserve(stringCount);
782 for (int i = 0; i < stringCount; i++) {
783 stringList.push_back(stream.read_string());
784 }
785 }
786
787 // Read elements
788 const auto elementCount = stream.read<int32_t>();
789 this->elements.reserve(elementCount);
790
791 for (int i = 0; i < elementCount; i++) {
792 this->elements.push_back(DMXElement{});
793 auto& element = this->elements.back();
794
795 element.setType(readStringFromIndex(stream));
796 if (elementNamesAndStringValuesAreStoredInStringList) {
797 element.setKey(readStringFromIndex(stream));
798 } else {
799 element.setKey(stream.read_string());
800 }
801 element.setUUID(stream.read_bytes<16>());
802 }
803
804 // Read element attributes
805 for (auto& element : this->elements) {
806 readAttributes(element, elementNamesAndStringValuesAreStoredInStringList);
807 }
808
809 return true;
810 };
811
812 bool parseSuccess = false;
813 switch (this->encodingType) {
814 case ENCODING_INVALID:
815 break;
818 case ENCODING_BINARY:
820 parseSuccess = readBinary();
821 break;
827 break;
828 }
829 if (!parseSuccess) {
831 this->elements.clear();
832 }
833}
834
835DMX::DMX(std::string_view dmxData)
836 : DMX{{reinterpret_cast<const std::byte*>(dmxData.data()), dmxData.size()}} {}
837
838DMX::operator bool() const {
839 return this->encodingType != ENCODING_INVALID;
840}
841
843 return this->encodingType;
844}
845
846void DMX::setEncodingType(Encoding encodingType_) {
847 this->encodingType = encodingType_;
849 this->encodingVersion = 1;
850 }
851}
852
856
857void DMX::shouldEncodingTypeHaveUnicodePrefix(bool encodingTypeHasUnicodePrefix_) {
858 this->encodingTypeHasUnicodePrefix = encodingTypeHasUnicodePrefix_;
859}
860
862 return this->encodingVersion;
863}
864
865void DMX::setEncodingVersion(int encodingVersion_) {
866 if (isEncodingVersionValid(this->encodingType, encodingVersion_)) {
867 this->encodingVersion = encodingVersion_;
868 }
869}
870
871std::string_view DMX::getFormatType() const {
872 return this->formatType;
873}
874
875void DMX::setFormatType(std::string formatType_) {
876 this->formatType = std::move(formatType_);
877}
878
880 return this->formatVersion;
881}
882
883void DMX::setFormatVersion(int formatVersion_) {
884 this->formatVersion = formatVersion_;
885}
886
891
893 return this->prefixAttributeContainers.size();
894}
895
896const std::vector<DMXElement>& DMX::getPrefixAttributeContainers() const {
897 return this->prefixAttributeContainers;
898}
899
900std::vector<DMXElement> & DMX::getPrefixAttributeContainers() {
901 return this->prefixAttributeContainers;
902}
903
905 if (this->prefixAttributeContainers.size() > n) {
906 this->prefixAttributeContainers.erase(this->prefixAttributeContainers.begin() + n);
907 }
908}
909
910bool DMX::hasElement(std::string_view key) const {
911 return !this->operator[](key);
912}
913
914DMXElement& DMX::addElement(std::string type, std::string key) {
915 DMXElement elem;
916 elem.setType(std::move(type));
917 elem.setKey(std::move(key));
919 this->elements.push_back(std::move(elem));
920 return this->elements.back();
921}
922
923uint64_t DMX::getElementCount() const {
924 return this->elements.size();
925}
926
927uint64_t DMX::getElementCount(std::string_view key) const {
928 uint64_t count = 0;
929 for (const auto& element : this->elements) {
930 if (string::iequals(element.getKey(), key)) {
931 ++count;
932 }
933 }
934 return count;
935}
936
937const std::vector<DMXElement>& DMX::getElements() const {
938 return this->elements;
939}
940
941std::vector<DMXElement>& DMX::getElements() {
942 return this->elements;
943}
944
945const DMXElement& DMX::operator[](unsigned int n) const {
946 return this->elements.at(n);
947}
948
949DMXElement& DMX::operator[](unsigned int n) {
950 return this->elements.at(n);
951}
952
953const DMXElement& DMX::operator[](std::string_view key) const {
954 return this->operator()(key);
955}
956
957DMXElement& DMX::operator[](std::string_view key) {
958 return this->operator()(key);
959}
960
961const DMXElement& DMX::operator()(std::string_view key) const {
962 for (const auto& element : this->elements) {
963 if (string::iequals(element.getKey(), key)) {
964 return element;
965 }
966 }
967 return getInvalidElement();
968}
969
970DMXElement& DMX::operator()(std::string_view key) {
971 for (auto& element : this->elements) {
972 if (string::iequals(element.getKey(), key)) {
973 return element;
974 }
975 }
976 return this->addElement("DmElement", std::string{key});
977}
978
979const DMXElement& DMX::operator()(std::string_view key, unsigned int n) const {
980 unsigned int count = 0;
981 for (const auto& element : this->elements) {
982 if (string::iequals(element.getKey(), key)) {
983 if (count == n) {
984 return element;
985 }
986 if (++count > n) {
987 break;
988 }
989 }
990 }
991 return getInvalidElement();
992}
993
994DMXElement& DMX::operator()(std::string_view key, unsigned int n) {
995 unsigned int count = 0;
996 for (auto& element: this->elements) {
997 if (string::iequals(element.getKey(), key)) {
998 if (count == n) {
999 return element;
1000 }
1001 if (++count > n) {
1002 break;
1003 }
1004 }
1005 }
1006 return this->addElement("DmElement", std::string{key});
1007}
1008
1009void DMX::removeElement(unsigned int n) {
1010 if (this->elements.size() > n) {
1011 this->elements.erase(this->elements.begin() + n);
1012 }
1013 for (auto& element : this->elements) {
1014 for (auto& attribute : element.attributes) {
1015 if (const auto attributeType = attribute.getValueType(); attributeType == DMXValue::ID::ELEMENT) {
1016 auto elementRef = attribute.getValue<DMXValue::Element>();
1017 if (elementRef.index > n) {
1018 elementRef.index--;
1019 }
1020 attribute.setValue(elementRef);
1021 } else if (attributeType == DMXValue::ID::ARRAY_ELEMENT) {
1022 auto elementsRef = attribute.getValue<std::vector<DMXValue::Element>>();
1023 bool shouldSet = false;
1024 for (auto& [index, externalGUID] : elementsRef) {
1025 if (index > n) {
1026 index--;
1027 shouldSet = true;
1028 }
1029 }
1030 if (shouldSet) {
1031 attribute.setValue(elementsRef);
1032 }
1033 }
1034 }
1035 }
1036}
1037
1038std::vector<std::byte> DMX::bake() const {
1040 return {};
1041 }
1042
1043 // todo: bake
1044 return {};
1045}
1046
1047void DMX::bake(const std::filesystem::path& dmxPath) const {
1048 fs::writeFileBuffer(dmxPath, this->bake());
1049}
1050
1052 static std::random_device random_device{};
1053 static std::mt19937 generator{random_device()};
1054 std::uniform_int_distribution<short> distribution{std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max()};
1055
1056 DMXValue::UUID uuid;
1057 for (auto& byte : uuid) {
1058 byte = static_cast<std::byte>(distribution(generator));
1059 }
1060 return uuid;
1061}
1062
1064 static DMXElement element;
1065 return element;
1066}
1067
1068DMX literals::operator ""_kv2(const char *str, std::size_t len) {
1069 return DMX{{str, len}};
1070}
bool isInvalid() const
Check if the given attribute is invalid.
Definition DMX.cpp:61
std::string key
Definition DMX.h:580
DMXAttribute()=default
DMXAttribute & operator=(DMXValue::Generic value_)
Set the value associated with the attribute.
Definition DMX.cpp:331
DMXValue::Generic value
Definition DMX.h:581
void setValue(DMXValue::Generic value_)
Set the value associated with the attribute.
Definition DMX.cpp:327
bool isValueArray() const
Definition DMX.cpp:81
const DMXValue::Generic & getValue() const
Definition DMX.cpp:85
DMXValue::ID getValueType() const
Definition DMX.cpp:77
void setKey(std::string key_)
Definition DMX.cpp:73
std::string getValueString() const
Definition DMX.cpp:89
std::string_view getKey() const
Definition DMX.cpp:69
static const DMXAttribute & getInvalidAttribute()
Definition DMX.cpp:483
uint64_t getAttributeCount() const
Get the number of child attributes.
Definition DMX.cpp:376
std::string key
Definition DMX.h:690
void setUUID(const DMXValue::UUID &guid_)
Definition DMX.cpp:360
void setKey(std::string key_)
Set the key associated with the element.
Definition DMX.cpp:352
bool hasAttribute(std::string_view attributeKey) const
Check if the element has one or more children with the given name.
Definition DMX.cpp:364
const DMXAttribute & operator()(std::string_view attributeKey) const
Get the first attribute of the element with the given key.
Definition DMX.cpp:414
std::string_view getType() const
Get the C++ type the element maps to.
Definition DMX.cpp:340
const DMXAttribute & operator[](unsigned int n) const
Get the attribute of the element at the given index.
Definition DMX.cpp:398
DMXAttribute & addAttribute(std::string key_, DMXValue::Generic value_={})
Add an attribute to the element.
Definition DMX.cpp:368
const std::vector< DMXAttribute > & getAttributes() const
Get the child attributes of the element.
Definition DMX.cpp:390
std::string type
Definition DMX.h:689
void setType(std::string type_)
Set the C++ type the element maps to.
Definition DMX.cpp:344
std::vector< DMXAttribute > attributes
Definition DMX.h:692
const DMXValue::UUID & getUUID() const
Definition DMX.cpp:356
void removeAttribute(unsigned int n)
Remove an attribute from the element.
Definition DMX.cpp:462
std::string_view getKey() const
Get the key associated with the element.
Definition DMX.cpp:348
DMXValue::UUID uuid
Definition DMX.h:691
std::vector< DMXElement > prefixAttributeContainers
Definition DMX.h:858
int encodingVersion
Definition DMX.h:854
const DMXElement & operator()(std::string_view key) const
Get the first element in the element list with the given key.
Definition DMX.cpp:961
int getFormatVersion() const
Definition DMX.cpp:879
DMX(Encoding encodingType_, int encodingVersion_, std::string formatType_, int formatVersion_)
Definition DMX.cpp:488
void removeElement(unsigned int n)
Remove an element from the element list and update all element references.
Definition DMX.cpp:1009
bool doesEncodingTypeHaveUnicodePrefix() const
Definition DMX.cpp:853
static const DMXElement & getInvalidElement()
Definition DMX.cpp:1063
Encoding
Definition DMX.h:697
@ ENCODING_KEYVALUES2
Definition DMX.h:705
@ ENCODING_BINARY_OLD
Definition DMX.h:699
@ ENCODING_BINARY
Definition DMX.h:701
@ ENCODING_BINARY_SEQIDS
Definition DMX.h:702
@ ENCODING_KEYVALUES2_FLAT
Definition DMX.h:706
@ ENCODING_INVALID
Definition DMX.h:698
@ ENCODING_KEYVALUES2_NOIDS
Definition DMX.h:707
@ ENCODING_KEYVALUES2_FLAT_OLD
Definition DMX.h:704
@ ENCODING_KEYVALUES2_OLD
Definition DMX.h:703
@ ENCODING_BINARY_OLD_SFM
Definition DMX.h:700
bool encodingTypeHasUnicodePrefix
Definition DMX.h:853
const std::vector< DMXElement > & getPrefixAttributeContainers() const
Definition DMX.cpp:896
void shouldEncodingTypeHaveUnicodePrefix(bool encodingTypeHasUnicodePrefix_)
Definition DMX.cpp:857
void setFormatType(std::string formatType_)
Definition DMX.cpp:875
std::vector< DMXElement > elements
Definition DMX.h:859
Encoding getEncodingType() const
Definition DMX.cpp:842
std::vector< std::byte > bake() const
Definition DMX.cpp:1038
uint64_t getElementCount() const
Get the number of elements.
Definition DMX.cpp:923
const std::vector< DMXElement > & getElements() const
Definition DMX.cpp:937
bool hasElement(std::string_view key) const
Check if the element list has one or more elements with the given name.
Definition DMX.cpp:910
void setEncodingVersion(int encodingVersion_)
Definition DMX.cpp:865
Encoding encodingType
Definition DMX.h:852
DMXElement & addPrefixAttributeContainer()
Definition DMX.cpp:887
int formatVersion
Definition DMX.h:856
void removePrefixAttributeContainer(unsigned int n)
Definition DMX.cpp:904
static DMXValue::UUID createRandomUUID()
Definition DMX.cpp:1051
const DMXElement & operator[](unsigned int n) const
Get the element in the element list at the given index.
Definition DMX.cpp:945
static constexpr bool isEncodingVersionValid(Encoding encodingType, int encodingVersion)
Definition DMX.h:824
std::string formatType
Definition DMX.h:855
void setFormatVersion(int formatVersion_)
Definition DMX.cpp:883
std::string_view getFormatType() const
Definition DMX.cpp:871
DMXElement & addElement(std::string type, std::string key)
Add an element to the element list.
Definition DMX.cpp:914
void setEncodingType(Encoding encodingType_)
Definition DMX.cpp:846
int getEncodingVersion() const
Definition DMX.cpp:861
uint64_t getPrefixAttributeContainerCount() const
Get the number of prefix attributes.
Definition DMX.cpp:892
IDVersion
Not representative of DMX encoding version, although the attribute type enum variances are linked to ...
Definition DMX.h:171
sourcepp::math::Mat4x4f Matrix4x4
Definition DMX.h:50
constexpr ID arrayIDToInnerID(ID id)
Definition DMX.h:500
std::variant< std::monostate, Element, int32_t, float, bool, std::string, std::vector< std::byte >, UUID, Time, Color, Vector2, Vector3, Vector4, EulerAngles, Quaternion, Matrix4x4, uint64_t, uint8_t, std::vector< Element >, std::vector< int32_t >, std::vector< float >, std::vector< bool >, std::vector< std::string >, std::vector< std::vector< std::byte > >, std::vector< UUID >, std::vector< Time >, std::vector< Color >, std::vector< Vector2 >, std::vector< Vector3 >, std::vector< Vector4 >, std::vector< EulerAngles >, std::vector< Quaternion >, std::vector< Matrix4x4 >, std::vector< uint64_t >, std::vector< uint8_t > > Generic
Definition DMX.h:460
std::array< std::byte, 16 > UUID
Definition DMX.h:17
sourcepp::math::Vec3f Vector3
Definition DMX.h:42
std::string idToString(ID id)
Definition DMX.cpp:19
constexpr ID decodeID(IDv1 id)
Definition DMX.h:221
sourcepp::math::Vec4f Vector4
Definition DMX.h:44
sourcepp::math::Vec2f Vector2
Definition DMX.h:40
Definition DMX.h:13
bool writeFileBuffer(const std::filesystem::path &filepath, std::span< const std::byte > buffer)
Definition FS.cpp:37
bool iequals(std::string_view s1, std::string_view s2)
Definition String.cpp:62
std::string externalGUID
Definition DMX.h:21