OpenXRVkAssetsValidator.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/std/string/regex.h>
  9. #include <OpenXRVk/OpenXRVkAssetsValidator.h>
  10. namespace OpenXRVkAssetsValidator
  11. {
  12. ///////////////////////////////////////////////////////////////////////////
  13. // Asset Validation Common Start
  14. // A regular Name is just a utf-8 string, it can contain upper case letters
  15. // and spaces in between. But it can not be empty, and can not contain leading to trailing spaces.
  16. static AZ::Outcome<void, AZStd::string> ValidateName(const AZStd::string& name)
  17. {
  18. if (name.empty())
  19. {
  20. return AZ::Failure("Name should not be empty.");
  21. }
  22. //Spaces at the beginning and end of the name are not allowed.
  23. AZStd::string tmpName(name);
  24. AZ::StringFunc::TrimWhiteSpace(tmpName, true, true);
  25. if (tmpName.empty())
  26. {
  27. return AZ::Failure("Name is just a bunch of spaces.");
  28. }
  29. if (tmpName.size() != name.size())
  30. {
  31. return AZ::Failure(
  32. AZStd::string::format("Trailing or leading spaces are not allowed in a Name [%s].",
  33. name.c_str())
  34. );
  35. }
  36. return AZ::Success();
  37. }
  38. // Unlike an OpenXR Name, a Localized Name is just a utf-8 string, it can contain upper case letters
  39. // and spaces in between. But it can not be empty, and can not contain leading to trailing spaces.
  40. static AZ::Outcome<void, AZStd::string> ValidateOpenXRLocalizedName(const AZStd::string& name)
  41. {
  42. auto outcome = ValidateName(name);
  43. if (!outcome.IsSuccess())
  44. {
  45. return AZ::Failure(
  46. AZStd::string::format("Localized Name [%s] is invalid. Reason:\n%s", name.c_str(), outcome.GetError().c_str())
  47. );
  48. }
  49. return AZ::Success();
  50. }
  51. // Asset Validation Common End
  52. ///////////////////////////////////////////////////////////////////////////
  53. ///////////////////////////////////////////////////////////////////////////
  54. // OpenXRInteractionProfilesAsset Validation Start
  55. static AZ::Outcome<void, AZStd::string> ValidateActionTypeString(const AZStd::string& actionTypeStr)
  56. {
  57. using CPD = OpenXRVk::OpenXRInteractionComponentPathDescriptor;
  58. static const AZStd::unordered_set<AZStd::string> ValidActionTypes{
  59. {CPD::s_TypeBoolStr},
  60. {CPD::s_TypeFloatStr},
  61. {CPD::s_TypeVector2Str},
  62. {CPD::s_TypePoseStr},
  63. {CPD::s_TypeVibrationStr}
  64. };
  65. if (!ValidActionTypes.contains(actionTypeStr))
  66. {
  67. static AZStd::string ValidListStr;
  68. if (ValidListStr.empty())
  69. {
  70. ValidListStr += "[ ";
  71. for (const auto& validActionTypeStr : ValidActionTypes)
  72. {
  73. if (!ValidListStr.empty())
  74. {
  75. ValidListStr += ", ";
  76. }
  77. ValidListStr += validActionTypeStr;
  78. }
  79. ValidListStr += " ]";
  80. }
  81. return AZ::Failure(
  82. AZStd::string::format("Action Type [%s] is invalid. It can only be one of %s",
  83. actionTypeStr.c_str(), ValidListStr.c_str())
  84. );
  85. }
  86. return AZ::Success();
  87. }
  88. // An OpenXR path string only contain characters as described here
  89. // https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#well-formed-path-strings
  90. static AZ::Outcome<void, AZStd::string> ValidateOpenXRPath(const AZStd::string& path)
  91. {
  92. static AZStd::regex s_validCharactersRegEx(R"(^(/[a-z0-9\-_\.]+)+$)", AZStd::regex::ECMAScript);
  93. if (!AZStd::regex_match(path, s_validCharactersRegEx))
  94. {
  95. return AZ::Failure(
  96. AZStd::string::format("The path [%s] contains an invalid character, or is missing a leading '/' or contains a leading '/'", path.c_str())
  97. );
  98. }
  99. return AZ::Success();
  100. }
  101. static AZ::Outcome<void, AZStd::string> ValidateComponentPathDescriptor(const OpenXRVk::OpenXRInteractionComponentPathDescriptor& componentPathDescriptor,
  102. AZStd::unordered_set<AZStd::string>& uniqueComponentPathNames, AZStd::unordered_set<AZStd::string>& uniqueComponentPathPaths)
  103. {
  104. {
  105. if (uniqueComponentPathNames.contains(componentPathDescriptor.m_name))
  106. {
  107. return AZ::Failure(
  108. AZStd::string::format("A Component Path with name [%s] already exists.",
  109. componentPathDescriptor.m_name.c_str())
  110. );
  111. }
  112. uniqueComponentPathNames.emplace(componentPathDescriptor.m_name);
  113. auto outcome = ValidateName(componentPathDescriptor.m_name);
  114. if (!outcome.IsSuccess())
  115. {
  116. return AZ::Failure(
  117. AZStd::string::format("Component Name[%s] is invalid.Reason:\n%s", componentPathDescriptor.m_name.c_str(), outcome.GetError().c_str())
  118. );
  119. }
  120. }
  121. {
  122. if (uniqueComponentPathPaths.contains(componentPathDescriptor.m_path))
  123. {
  124. return AZ::Failure(
  125. AZStd::string::format("A Component Path with path [%s] already exists.",
  126. componentPathDescriptor.m_path.c_str())
  127. );
  128. }
  129. uniqueComponentPathPaths.emplace(componentPathDescriptor.m_path);
  130. auto outcome = ValidateOpenXRPath(componentPathDescriptor.m_path);
  131. if (!outcome.IsSuccess())
  132. {
  133. return AZ::Failure(
  134. AZStd::string::format("Component Path path [%s] is invalid. Reason:\n%s", componentPathDescriptor.m_path.c_str(), outcome.GetError().c_str())
  135. );
  136. }
  137. }
  138. auto outcome = ValidateActionTypeString(componentPathDescriptor.m_actionTypeStr);
  139. if (!outcome.IsSuccess())
  140. {
  141. return AZ::Failure(
  142. AZStd::string::format("Component Path path [%s] has an invalid action type. Reason:\n%s", componentPathDescriptor.m_name.c_str(), outcome.GetError().c_str())
  143. );
  144. }
  145. return AZ::Success();
  146. }
  147. static AZ::Outcome<void, AZStd::string> ValidateUserPathDescriptor(const OpenXRVk::OpenXRInteractionUserPathDescriptor& userPathDescriptor,
  148. AZStd::unordered_set<AZStd::string>& uniqueUserPathNames, AZStd::unordered_set<AZStd::string>& uniqueUserPathPaths,
  149. AZStd::unordered_set<AZStd::string>& uniqueComponentPathNames, AZStd::unordered_set<AZStd::string>& uniqueComponentPathPaths)
  150. {
  151. {
  152. if (uniqueUserPathNames.contains(userPathDescriptor.m_name))
  153. {
  154. return AZ::Failure(
  155. AZStd::string::format("An User Path with name [%s] already exists.",
  156. userPathDescriptor.m_name.c_str())
  157. );
  158. }
  159. uniqueUserPathNames.emplace(userPathDescriptor.m_name);
  160. auto outcome = ValidateName(userPathDescriptor.m_name);
  161. if (!outcome.IsSuccess())
  162. {
  163. return AZ::Failure(
  164. AZStd::string::format("User Path Name [%s] is invalid. Reason:\n%s", userPathDescriptor.m_name.c_str(), outcome.GetError().c_str())
  165. );
  166. }
  167. }
  168. {
  169. if (uniqueUserPathPaths.contains(userPathDescriptor.m_path))
  170. {
  171. return AZ::Failure(
  172. AZStd::string::format("User Path with path [%s] already exists.",
  173. userPathDescriptor.m_path.c_str())
  174. );
  175. }
  176. uniqueUserPathPaths.emplace(userPathDescriptor.m_path);
  177. auto outcome = ValidateOpenXRPath(userPathDescriptor.m_path);
  178. if (!outcome.IsSuccess())
  179. {
  180. return AZ::Failure(
  181. AZStd::string::format("User Path path [%s] is invalid. Reason:\n%s", userPathDescriptor.m_path.c_str(), outcome.GetError().c_str())
  182. );
  183. }
  184. }
  185. for (const auto& componentPathDescriptor : userPathDescriptor.m_componentPathDescriptors)
  186. {
  187. auto outcome = ValidateComponentPathDescriptor(componentPathDescriptor,
  188. uniqueComponentPathNames, uniqueComponentPathPaths);
  189. if (!outcome.IsSuccess())
  190. {
  191. return AZ::Failure(
  192. AZStd::string::format("Invalid Component Path [%s]. Reason:\n%s", componentPathDescriptor.m_name.c_str(), outcome.GetError().c_str())
  193. );
  194. }
  195. }
  196. return AZ::Success();
  197. }
  198. static AZ::Outcome<void, AZStd::string> ValidateInteractionProfileDescriptor(
  199. const OpenXRVk::OpenXRInteractionProfileDescriptor& interactionProfileDescriptor,
  200. AZStd::unordered_set<AZStd::string>& uniqueNames, AZStd::unordered_set<AZStd::string>& uniquePaths)
  201. {
  202. {
  203. if (uniqueNames.contains(interactionProfileDescriptor.m_name))
  204. {
  205. return AZ::Failure(
  206. AZStd::string::format("Interaction Profile with name [%s] already exists.",
  207. interactionProfileDescriptor.m_name.c_str())
  208. );
  209. }
  210. uniqueNames.emplace(interactionProfileDescriptor.m_name);
  211. auto outcome = ValidateName(interactionProfileDescriptor.m_name);
  212. if (!outcome.IsSuccess())
  213. {
  214. return AZ::Failure(
  215. AZStd::string::format("Interaction Profile Unique Name [%s] is invalid. Reason:\n%s", interactionProfileDescriptor.m_name.c_str(), outcome.GetError().c_str())
  216. );
  217. }
  218. }
  219. {
  220. if (uniquePaths.contains(interactionProfileDescriptor.m_path))
  221. {
  222. return AZ::Failure(
  223. AZStd::string::format("Interaction Profile with path [%s] already exists.",
  224. interactionProfileDescriptor.m_path.c_str())
  225. );
  226. }
  227. uniquePaths.emplace(interactionProfileDescriptor.m_path);
  228. auto outcome = ValidateOpenXRPath(interactionProfileDescriptor.m_path);
  229. if (!outcome.IsSuccess())
  230. {
  231. return AZ::Failure(
  232. AZStd::string::format("Interaction Profile Path [%s] is invalid. Reason:\n%s", interactionProfileDescriptor.m_path.c_str(), outcome.GetError().c_str())
  233. );
  234. }
  235. }
  236. AZStd::unordered_set<AZStd::string> uniqueUserPathNames;
  237. AZStd::unordered_set<AZStd::string> uniqueUserPathPaths;
  238. AZStd::unordered_set<AZStd::string> uniqueComponentPathNames;
  239. AZStd::unordered_set<AZStd::string> uniqueComponentPathPaths;
  240. for (const auto& userPathDescriptor : interactionProfileDescriptor.m_userPathDescriptors)
  241. {
  242. auto outcome = ValidateUserPathDescriptor(userPathDescriptor,
  243. uniqueUserPathNames, uniqueUserPathPaths, uniqueComponentPathNames, uniqueComponentPathPaths);
  244. if (!outcome.IsSuccess())
  245. {
  246. return AZ::Failure(
  247. AZStd::string::format("Invalid User Path [%s]. Reason:\n%s", userPathDescriptor.m_name.c_str(), outcome.GetError().c_str())
  248. );
  249. }
  250. }
  251. for (const auto& componentPathDescriptor : interactionProfileDescriptor.m_commonComponentPathDescriptors)
  252. {
  253. auto outcome = ValidateComponentPathDescriptor(componentPathDescriptor,
  254. uniqueComponentPathNames, uniqueComponentPathPaths);
  255. if (!outcome.IsSuccess())
  256. {
  257. return AZ::Failure(
  258. AZStd::string::format("Invalid Common Component Path [%s]. Reason:\n%s", componentPathDescriptor.m_name.c_str(), outcome.GetError().c_str())
  259. );
  260. }
  261. }
  262. return AZ::Success();
  263. }
  264. AZ::Outcome<void, AZStd::string> ValidateInteractionProfilesAsset(
  265. const OpenXRVk::OpenXRInteractionProfilesAsset& interactionProfilesAsset)
  266. {
  267. if (interactionProfilesAsset.m_interactionProfileDescriptors.empty())
  268. {
  269. return AZ::Failure("InteractionProfiles asset requires at least one Interaction Profile.");
  270. }
  271. AZStd::unordered_set<AZStd::string> uniqueNames;
  272. AZStd::unordered_set<AZStd::string> uniquePaths;
  273. uint32_t i = 0;
  274. for (const auto& interactionProfileDescriptor : interactionProfilesAsset.m_interactionProfileDescriptors)
  275. {
  276. auto outcome = ValidateInteractionProfileDescriptor(interactionProfileDescriptor,
  277. uniqueNames, uniquePaths);
  278. if (!outcome.IsSuccess())
  279. {
  280. return AZ::Failure(
  281. AZStd::string::format("InteractionProfile[%u] is invalid. Reason:\n%s",
  282. i, outcome.GetError().c_str())
  283. );
  284. }
  285. i++;
  286. }
  287. return AZ::Success();
  288. }
  289. // OpenXRInteractionProfilesAsset Validation End
  290. ///////////////////////////////////////////////////////////////////////////
  291. ///////////////////////////////////////////////////////////////////////////
  292. // OpenXRActionSetsAsset Validation Start
  293. static AZ::Outcome<void, AZStd::string> ValidateActionPathDescriptor(const OpenXRVk::OpenXRActionPathDescriptor& actionPathDescriptor,
  294. const OpenXRVk::OpenXRInteractionProfilesAsset& interactionProfilesAsset,
  295. AZStd::unordered_set<AZStd::string>& uniqueActionPaths)
  296. {
  297. auto concatenatedActionPath = actionPathDescriptor.m_interactionProfileName
  298. + actionPathDescriptor.m_userPathName
  299. + actionPathDescriptor.m_componentPathName;
  300. if (uniqueActionPaths.contains(concatenatedActionPath))
  301. {
  302. return AZ::Failure(
  303. AZStd::string::format("An Action Path with profile[%s], userPath[%s], componentPath[%s] already exists.",
  304. actionPathDescriptor.m_interactionProfileName.c_str(),
  305. actionPathDescriptor.m_userPathName.c_str(),
  306. actionPathDescriptor.m_componentPathName.c_str())
  307. );
  308. }
  309. uniqueActionPaths.emplace(AZStd::move(concatenatedActionPath));
  310. if (actionPathDescriptor.m_interactionProfileName.empty())
  311. {
  312. return AZ::Failure(
  313. AZStd::string::format("ActionPath Descriptor must have an InteractionProfile name.")
  314. );
  315. }
  316. const auto interactionProfileDescriptorPtr = interactionProfilesAsset.GetInteractionProfileDescriptor(actionPathDescriptor.m_interactionProfileName);
  317. if (!interactionProfileDescriptorPtr)
  318. {
  319. return AZ::Failure(
  320. AZStd::string::format("Unknown Interaction Profile Descriptor named [%s].",
  321. actionPathDescriptor.m_interactionProfileName.c_str())
  322. );
  323. }
  324. if (actionPathDescriptor.m_userPathName.empty())
  325. {
  326. return AZ::Failure(
  327. AZStd::string::format("ActionPath Descriptor must have an UserPath name.")
  328. );
  329. }
  330. const auto userPathDescriptorPtr = interactionProfileDescriptorPtr->GetUserPathDescriptor(actionPathDescriptor.m_userPathName);
  331. if (!userPathDescriptorPtr)
  332. {
  333. return AZ::Failure(
  334. AZStd::string::format("Unknown UserPath descriptor named [%s].",
  335. actionPathDescriptor.m_userPathName.c_str())
  336. );
  337. }
  338. if (actionPathDescriptor.m_componentPathName.empty())
  339. {
  340. return AZ::Failure(
  341. AZStd::string::format("ActionPath Descriptor must have a ComponentPath name.")
  342. );
  343. }
  344. const auto componentPathDescriptorPtr = interactionProfileDescriptorPtr->GetComponentPathDescriptor(*userPathDescriptorPtr, actionPathDescriptor.m_componentPathName);
  345. if (!componentPathDescriptorPtr)
  346. {
  347. return AZ::Failure(
  348. AZStd::string::format("Unknown ComponentPath descriptor named [%s].",
  349. actionPathDescriptor.m_componentPathName.c_str())
  350. );
  351. }
  352. return AZ::Success();
  353. }
  354. static const AZStd::string& GetActionTypeStringFromActionPathDescriptor(
  355. const OpenXRVk::OpenXRInteractionProfilesAsset& interactionProfilesAsset,
  356. const OpenXRVk::OpenXRActionPathDescriptor& actionPathDescriptor
  357. )
  358. {
  359. return interactionProfilesAsset.GetActionPathTypeStr(
  360. actionPathDescriptor.m_interactionProfileName,
  361. actionPathDescriptor.m_userPathName,
  362. actionPathDescriptor.m_componentPathName
  363. );
  364. }
  365. static bool IsActionTypeBoolOrFloat(const AZStd::string& actionTypeStr)
  366. {
  367. return (
  368. (actionTypeStr == OpenXRVk::OpenXRInteractionComponentPathDescriptor::s_TypeBoolStr) ||
  369. (actionTypeStr == OpenXRVk::OpenXRInteractionComponentPathDescriptor::s_TypeFloatStr)
  370. );
  371. }
  372. static bool AreCompatibleActionTypeStrings(const AZStd::string& lhs, const AZStd::string& rhs)
  373. {
  374. if (IsActionTypeBoolOrFloat(lhs) && IsActionTypeBoolOrFloat(rhs))
  375. {
  376. return true;
  377. }
  378. return (lhs == rhs);
  379. }
  380. // An OpenXR name string only contain characters which are allowed in a SINGLE LEVEL of a well-formed path string
  381. // https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#well-formed-path-strings
  382. static AZ::Outcome<void, AZStd::string> ValidateOpenXRName(const AZStd::string& name)
  383. {
  384. static AZStd::regex s_validCharactersRegEx(R"(^[a-z0-9\-_\.]+$)", AZStd::regex::ECMAScript);
  385. if (!AZStd::regex_match(name, s_validCharactersRegEx))
  386. {
  387. return AZ::Failure(
  388. AZStd::string::format("The name [%s] contains an invalid character", name.c_str())
  389. );
  390. }
  391. return AZ::Success();
  392. }
  393. static AZ::Outcome<void, AZStd::string> ValidateActionDescriptor(
  394. const OpenXRVk::OpenXRInteractionProfilesAsset& interactionProfilesAsset,
  395. const OpenXRVk::OpenXRActionDescriptor& actionDescriptor,
  396. AZStd::unordered_set<AZStd::string>& uniqueActionNames,
  397. AZStd::unordered_set<AZStd::string>& uniqueActionLocalizedNames)
  398. {
  399. {
  400. if (uniqueActionNames.contains(actionDescriptor.m_name))
  401. {
  402. return AZ::Failure(
  403. AZStd::string::format("An Action with name [%s] already exists.",
  404. actionDescriptor.m_name.c_str())
  405. );
  406. }
  407. uniqueActionNames.emplace(actionDescriptor.m_name);
  408. auto outcome = ValidateOpenXRName(actionDescriptor.m_name);
  409. if (!outcome.IsSuccess())
  410. {
  411. return AZ::Failure(
  412. AZStd::string::format("Failed to validate Action Descriptor named=[%s].\nReason:\n%s",
  413. actionDescriptor.m_name.c_str(), outcome.GetError().c_str())
  414. );
  415. }
  416. }
  417. // Only validate if not empty. If empty, the asset builder will force this to be a copy of
  418. // actionDescriptor.m_name.
  419. if (!actionDescriptor.m_localizedName.empty())
  420. {
  421. if (uniqueActionLocalizedNames.contains(actionDescriptor.m_localizedName))
  422. {
  423. return AZ::Failure(
  424. AZStd::string::format("An Action with localized name [%s] already exists.",
  425. actionDescriptor.m_localizedName.c_str())
  426. );
  427. }
  428. uniqueActionLocalizedNames.emplace(actionDescriptor.m_localizedName);
  429. auto outcome = ValidateOpenXRLocalizedName(actionDescriptor.m_localizedName);
  430. if (!outcome.IsSuccess())
  431. {
  432. return AZ::Failure(
  433. AZStd::string::format("Failed to validate localized name of Action Descriptor named=[%s]\nReason:\n%s",
  434. actionDescriptor.m_name.c_str(), outcome.GetError().c_str())
  435. );
  436. }
  437. }
  438. if (actionDescriptor.m_actionPathDescriptors.empty())
  439. {
  440. return AZ::Failure(
  441. AZStd::string::format("At least one ActionPath Descriptor is required by Action Descriptor named=[%s].\n",
  442. actionDescriptor.m_name.c_str())
  443. );
  444. }
  445. AZStd::unordered_set<AZStd::string> uniqueActionPaths;
  446. // It is very important that all action path descriptors have compatible data types.
  447. const AZStd::string& firstActionTypeStr = GetActionTypeStringFromActionPathDescriptor(
  448. interactionProfilesAsset, actionDescriptor.m_actionPathDescriptors[0]);
  449. uint32_t actionPathIndex = 0;
  450. for (const auto& actionPathDescriptor : actionDescriptor.m_actionPathDescriptors)
  451. {
  452. auto outcome = ValidateActionPathDescriptor(actionPathDescriptor, interactionProfilesAsset, uniqueActionPaths);
  453. if (!outcome.IsSuccess())
  454. {
  455. return AZ::Failure(
  456. AZStd::string::format("Failed to validate Action Path Descriptor for Action Descriptor named=[%s].\nReason:\n%s",
  457. actionDescriptor.m_name.c_str(), outcome.GetError().c_str())
  458. );
  459. }
  460. const AZStd::string& actionTypeStr = GetActionTypeStringFromActionPathDescriptor(
  461. interactionProfilesAsset, actionPathDescriptor);
  462. if (!AreCompatibleActionTypeStrings(firstActionTypeStr, actionTypeStr))
  463. {
  464. return AZ::Failure(
  465. AZStd::string::format("ActionType=[%s] of ActionPath Descriptor[%u] is NOT compatible with the ActionType=[%s] ActionPath Descriptor[0]",
  466. actionTypeStr.c_str(), actionPathIndex, firstActionTypeStr.c_str())
  467. );
  468. }
  469. actionPathIndex++;
  470. }
  471. return AZ::Success();
  472. }
  473. AZ::Outcome<void, AZStd::string> ValidateActionSetsAsset(const OpenXRVk::OpenXRActionSetsAsset& actionSetsAsset,
  474. const OpenXRVk::OpenXRInteractionProfilesAsset& interactionProfilesAsset)
  475. {
  476. if (actionSetsAsset.m_actionSetDescriptors.empty())
  477. {
  478. return AZ::Failure("At least one ActionSet must be listed in an ActionSets asset");
  479. }
  480. AZStd::unordered_set<AZStd::string> uniqueActionSetNames;
  481. AZStd::unordered_set<AZStd::string> uniqueActionSetLocalizedNames;
  482. for (const auto& actionSetDescriptor : actionSetsAsset.m_actionSetDescriptors)
  483. {
  484. {
  485. if (uniqueActionSetNames.contains(actionSetDescriptor.m_name))
  486. {
  487. return AZ::Failure(
  488. AZStd::string::format("An ActionSet named=[%s] already exists.",
  489. actionSetDescriptor.m_name.c_str())
  490. );
  491. }
  492. uniqueActionSetNames.emplace(actionSetDescriptor.m_name);
  493. auto outcome = ValidateOpenXRName(actionSetDescriptor.m_name);
  494. if (!outcome.IsSuccess())
  495. {
  496. return AZ::Failure(
  497. AZStd::string::format("Failed to validate ActionSet Descriptor name=[%s]. Reason:\n%s",
  498. actionSetDescriptor.m_name.c_str(), outcome.GetError().c_str())
  499. );
  500. }
  501. }
  502. // Only validate if not empty. If empty, the asset builder will force this to be a copy of
  503. // actionSetDescriptor.m_name.
  504. if (!actionSetDescriptor.m_localizedName.empty())
  505. {
  506. if (uniqueActionSetLocalizedNames.contains(actionSetDescriptor.m_localizedName))
  507. {
  508. return AZ::Failure(
  509. AZStd::string::format("An ActionSet with localized named=[%s] already exists.",
  510. actionSetDescriptor.m_localizedName.c_str())
  511. );
  512. }
  513. uniqueActionSetLocalizedNames.emplace(actionSetDescriptor.m_localizedName);
  514. auto outcome = ValidateOpenXRLocalizedName(actionSetDescriptor.m_localizedName);
  515. if (!outcome.IsSuccess())
  516. {
  517. return AZ::Failure(
  518. AZStd::string::format("Failed to validate ActionSet Descriptor name=[%s]. Reason:\n%s",
  519. actionSetDescriptor.m_name.c_str(), outcome.GetError().c_str())
  520. );
  521. }
  522. }
  523. if (actionSetDescriptor.m_actionDescriptors.empty())
  524. {
  525. return AZ::Failure(
  526. AZStd::string::format("ActionSet [%s] must contain at least one ActionDescriptor.",
  527. actionSetDescriptor.m_name.c_str())
  528. );
  529. }
  530. AZStd::unordered_set<AZStd::string> uniqueActionNames;
  531. AZStd::unordered_set<AZStd::string> uniqueActionLocalizedNames;
  532. for (const auto& actionDescriptor : actionSetDescriptor.m_actionDescriptors)
  533. {
  534. auto outcome = ValidateActionDescriptor(interactionProfilesAsset, actionDescriptor,
  535. uniqueActionNames, uniqueActionLocalizedNames);
  536. if (!outcome.IsSuccess())
  537. {
  538. return AZ::Failure(
  539. AZStd::string::format("Failed to validate ActionSet Descriptor name=[%s]. Reason:\n%s",
  540. actionSetDescriptor.m_name.c_str(), outcome.GetError().c_str())
  541. );
  542. }
  543. }
  544. }
  545. return AZ::Success();
  546. }
  547. // OpenXRActionSetsAsset Validation End
  548. ///////////////////////////////////////////////////////////////////////////
  549. } // namespace OpenXRVkAssetsValidator