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