MaterialVersionUpdate.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  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 <Atom/RPI.Reflect/Material/MaterialAsset.h>
  9. #include <Atom/RPI.Reflect/Material/MaterialVersionUpdate.h>
  10. #include <AzCore/Serialization/SerializeContext.h>
  11. #include <AzCore/Debug/Trace.h>
  12. namespace AZ
  13. {
  14. namespace RPI
  15. {
  16. void MaterialVersionUpdate::MaterialPropertyValueWrapper::Reflect(ReflectContext* context)
  17. {
  18. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  19. {
  20. serializeContext->Class<MaterialVersionUpdate::MaterialPropertyValueWrapper>()
  21. ->Version(0)
  22. ->Field("Value", &MaterialPropertyValueWrapper::m_value)
  23. ->Field("NameCache", &MaterialPropertyValueWrapper::m_nameCache)
  24. ;
  25. }
  26. }
  27. void MaterialVersionUpdate::Action::Reflect(ReflectContext* context)
  28. {
  29. MaterialVersionUpdate::MaterialPropertyValueWrapper::Reflect(context);
  30. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  31. {
  32. serializeContext->RegisterGenericType<MaterialVersionUpdate::Action::ArgsMap>();
  33. serializeContext->Class<MaterialVersionUpdate::Action>()
  34. ->Version(3) // Generic actions based on string -> MaterialPropertyValueWrapper map
  35. ->Field("ArgsMap", &Action::m_argsMap)
  36. ->Field("Operation", &Action::m_operation)
  37. ;
  38. }
  39. }
  40. void MaterialVersionUpdate::Reflect(ReflectContext* context)
  41. {
  42. MaterialVersionUpdate::Action::Reflect(context);
  43. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  44. {
  45. serializeContext->RegisterGenericType<MaterialVersionUpdate::Actions>();
  46. serializeContext->Class<MaterialVersionUpdate>()
  47. ->Version(2) // Generic actions based on string -> MaterialPropertyValue map
  48. ->Field("ToVersion", &MaterialVersionUpdate::m_toVersion)
  49. ->Field("Actions", &MaterialVersionUpdate::m_actions)
  50. ;
  51. }
  52. }
  53. void MaterialVersionUpdates::Reflect(ReflectContext* context)
  54. {
  55. MaterialVersionUpdate::Reflect(context);
  56. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  57. {
  58. serializeContext->RegisterGenericType<MaterialVersionUpdateList>();
  59. serializeContext->Class<MaterialVersionUpdates>()
  60. ->Version(0)
  61. ->Field("VersionUpdates", &MaterialVersionUpdates::m_versionUpdates)
  62. ;
  63. }
  64. }
  65. MaterialVersionUpdate::MaterialPropertyValueWrapper::MaterialPropertyValueWrapper(const MaterialPropertyValue& value)
  66. : m_value(value)
  67. {
  68. if (m_value.IsValid() && m_value.Is<AZStd::string>())
  69. {
  70. m_nameCache = AZ::Name(m_value.GetValue<AZStd::string>());
  71. }
  72. }
  73. const MaterialPropertyValue& MaterialVersionUpdate::MaterialPropertyValueWrapper::Get() const
  74. {
  75. return m_value;
  76. }
  77. const AZ::Name& MaterialVersionUpdate::MaterialPropertyValueWrapper::GetAsName() const
  78. {
  79. AZ_Error(
  80. "MaterialVersionUpdate", m_value.IsValid() && m_value.Is<AZStd::string>(),
  81. "GetAsName() expects a valid string value");
  82. return m_nameCache;
  83. }
  84. bool MaterialVersionUpdate::MaterialPropertyValueWrapper::operator==(const MaterialPropertyValueWrapper& other) const
  85. {
  86. return m_value == other.m_value;
  87. }
  88. MaterialVersionUpdate::MaterialVersionUpdate(uint32_t toVersion)
  89. : m_toVersion(toVersion)
  90. {
  91. }
  92. uint32_t MaterialVersionUpdate::GetVersion() const
  93. {
  94. return m_toVersion;
  95. }
  96. void MaterialVersionUpdate::SetVersion(uint32_t toVersion)
  97. {
  98. m_toVersion = toVersion;
  99. }
  100. bool MaterialVersionUpdate::Action::HasExpectedNumArguments(
  101. size_t expectedNum, const char* expectedArgs, AZStd::function<void(const char*)> onError) const
  102. {
  103. bool isValid = expectedNum == GetArgCount();
  104. if (!isValid && onError != nullptr)
  105. {
  106. onError(AZStd::string::format(
  107. "Expected %zu arguments in '%s' version update (%s), but found %zu",
  108. expectedNum, m_operation.GetCStr(), expectedArgs, GetArgCount())
  109. .c_str());
  110. }
  111. return isValid;
  112. }
  113. template <typename T>
  114. bool MaterialVersionUpdate::Action::HasExpectedArgument(
  115. const char* expectedArgName, const char* T_str, AZStd::function<void(const char*)> onError) const
  116. {
  117. const MaterialPropertyValue& val = GetArg(AZ::Name{ expectedArgName });
  118. bool isValid = val.IsValid() && val.Is<T>();
  119. if (!isValid && onError != nullptr)
  120. {
  121. onError(AZStd::string::format(
  122. "Expected a '%s' field in '%s' of type %s", expectedArgName, m_operation.GetCStr(), T_str)
  123. .c_str());
  124. }
  125. return isValid;
  126. }
  127. bool MaterialVersionUpdate::Action::HasExpectedArgumentAnyType(
  128. const char* expectedArgName, AZStd::function<void(const char*)> onError) const
  129. {
  130. const MaterialPropertyValue& val = GetArg(AZ::Name{ expectedArgName });
  131. bool isValid = val.IsValid();
  132. if (!isValid && onError != nullptr)
  133. {
  134. onError(AZStd::string::format(
  135. "Expected a '%s' field in '%s'", expectedArgName, m_operation.GetCStr())
  136. .c_str());
  137. }
  138. return isValid;
  139. }
  140. bool MaterialVersionUpdate::ValidateActions(
  141. const PropertyHelper* propertyHelper, AZStd::function<void(const char*)> onError) const
  142. {
  143. for (const auto& action : m_actions)
  144. {
  145. bool actionValidation;
  146. if (propertyHelper == nullptr)
  147. {
  148. actionValidation = action.Validate(onError);
  149. }
  150. else
  151. {
  152. actionValidation = action.ValidateFully(*propertyHelper, onError);
  153. }
  154. if (!actionValidation)
  155. {
  156. return false;
  157. }
  158. }
  159. return true;
  160. }
  161. bool MaterialVersionUpdate::Action::Validate(AZStd::function<void(const char*)> onError) const
  162. {
  163. bool error = false;
  164. if (m_operation == AZ::Name("rename") || m_operation == AZ::Name("renamePrefix"))
  165. {
  166. error = error || !HasExpectedNumArguments(2, "'from', 'to'", onError);
  167. error = error || !HasExpectedArgument<AZStd::string>("from", "string", onError);
  168. error = error || !HasExpectedArgument<AZStd::string>("to", "string", onError);
  169. }
  170. else if (m_operation == AZ::Name("setValue"))
  171. {
  172. error = error || !HasExpectedNumArguments(2, "'name', 'value'", onError);
  173. error = error || !HasExpectedArgument<AZStd::string>("name", "string", onError);
  174. error = error || !HasExpectedArgumentAnyType("value", onError);
  175. }
  176. else if (m_operation.IsEmpty())
  177. {
  178. if (onError != nullptr)
  179. {
  180. onError(AZStd::string::format(
  181. "Material version update action was not properly initialized: empty operation").c_str());
  182. }
  183. return false;
  184. }
  185. else
  186. {
  187. if (onError != nullptr)
  188. {
  189. onError(AZStd::string::format(
  190. "Unknown operation '%s' in material version update action",
  191. m_operation.GetCStr()).c_str());
  192. }
  193. return false;
  194. }
  195. return !error;
  196. }
  197. bool MaterialVersionUpdate::Action::ValidateFully(
  198. const PropertyHelper& propertyHelper,
  199. AZStd::function<void(const char*)> onError) const
  200. {
  201. if (!Validate(onError))
  202. {
  203. return false;
  204. }
  205. if (m_operation == AZ::Name("setValue"))
  206. {
  207. // Check property name & value type
  208. const AZ::Name& nameToSet = GetArgAsName(AZ::Name("name"));
  209. MaterialPropertyValue valueToSet = GetArg(AZ::Name("value"));
  210. if (!propertyHelper.CastToExpectedType(nameToSet, valueToSet, onError))
  211. {
  212. return false;
  213. }
  214. }
  215. return true;
  216. }
  217. bool MaterialVersionUpdate::ApplyPropertyRenames(AZ::Name& propertyId) const
  218. {
  219. bool renamed = false;
  220. for (const auto& action : m_actions)
  221. {
  222. if (action.GetOperation() == AZ::Name("rename"))
  223. {
  224. const AZ::Name& from = action.GetArgAsName(AZ::Name("from"));
  225. if (propertyId == from)
  226. {
  227. propertyId = action.GetArgAsName(AZ::Name("to"));
  228. renamed = true;
  229. }
  230. }
  231. else if (action.GetOperation() == AZ::Name("renamePrefix"))
  232. {
  233. const AZ::Name& from = action.GetArgAsName(AZ::Name("from"));
  234. if (propertyId.GetStringView().starts_with(from.GetCStr()))
  235. {
  236. AZStd::string renamedProperty = propertyId.GetCStr();
  237. AzFramework::StringFunc::Replace(renamedProperty, action.GetArgAsName(AZ::Name("from")).GetCStr(), action.GetArgAsName(AZ::Name("to")).GetCStr(), true, true);
  238. propertyId = Name{renamedProperty};
  239. renamed = true;
  240. }
  241. }
  242. }
  243. return renamed;
  244. }
  245. MaterialVersionUpdate::PropertyHelper::PropertyHelper(
  246. AZStd::function<bool(AZ::Name&)> applyAllPropertyRenames,
  247. const MaterialPropertiesLayout* materialPropertiesLayout)
  248. : m_applyAllPropertyRenames(applyAllPropertyRenames), m_materialPropertiesLayout(materialPropertiesLayout)
  249. {
  250. }
  251. bool MaterialVersionUpdate::PropertyHelper::CastToExpectedType(
  252. const Name& providedPropertyId, MaterialPropertyValue& value, AZStd::function<void(const char*)> onError) const
  253. {
  254. AZ_Assert(m_materialPropertiesLayout != nullptr, "PropertyHelper is not properly initialized");
  255. // Update property id to latest name
  256. Name propertyId(providedPropertyId);
  257. ApplyAllPropertyRenames(propertyId);
  258. // Check that the property is known
  259. const auto propertyIndex = m_materialPropertiesLayout->FindPropertyIndex(propertyId);
  260. if (propertyIndex.IsNull())
  261. {
  262. if (onError != nullptr)
  263. {
  264. onError(AZStd::string::format(
  265. "Could not find property %s in the material properties layout",
  266. FriendlyPropertyName(providedPropertyId, propertyId).c_str())
  267. .c_str());
  268. }
  269. return false;
  270. }
  271. // Due to the ambiguity in the json parser (e.g. Color vs Vector[3-4]): try to cast
  272. // the value into the correct type.
  273. const MaterialPropertyDescriptor *descriptor = m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex);
  274. TypeId expectedType = descriptor->GetAssetDataTypeId();
  275. value = value.CastToType(expectedType);
  276. // Check if that cast was successful
  277. if (value.GetTypeId() != expectedType)
  278. {
  279. if (onError != nullptr)
  280. {
  281. onError(AZStd::string::format(
  282. "Unexpected type for property %s: expected %s but received %s",
  283. FriendlyPropertyName(providedPropertyId, propertyId).c_str(),
  284. expectedType.ToString<AZStd::string>().c_str(),
  285. value.GetTypeId().ToString<AZStd::string>().c_str())
  286. .c_str());
  287. }
  288. return false;
  289. }
  290. return true;
  291. }
  292. bool MaterialVersionUpdate::PropertyHelper::ApplyAllPropertyRenames(AZ::Name& propertyId) const
  293. {
  294. return m_applyAllPropertyRenames(propertyId);
  295. }
  296. AZStd::string MaterialVersionUpdate::PropertyHelper::FriendlyPropertyName(
  297. const AZ::Name& propertyId, const AZ::Name& finalPropertyId) const
  298. {
  299. if (propertyId == finalPropertyId)
  300. {
  301. return AZStd::string::format("'%s'", propertyId.GetCStr());
  302. }
  303. else
  304. {
  305. return AZStd::string::format(
  306. "'%s' (final name of this property: '%s')", propertyId.GetCStr(), finalPropertyId.GetCStr());
  307. }
  308. }
  309. bool MaterialVersionUpdate::ApplySetValues(
  310. AZStd::vector<AZStd::pair<Name, MaterialPropertyValue>>& rawProperties,
  311. const PropertyHelper& propertyHelper,
  312. AZStd::function<void(const char*)> onError) const
  313. {
  314. bool valueWasSet = false;
  315. for (const auto& action : m_actions)
  316. {
  317. if (action.GetOperation() != AZ::Name("setValue"))
  318. {
  319. continue;
  320. }
  321. const AZ::Name& nameFromSetValueAction = action.GetArgAsName(AZ::Name("name"));
  322. // Update the name in case our setValue action is still using an older name
  323. AZ::Name nameToSet(nameFromSetValueAction);
  324. propertyHelper.ApplyAllPropertyRenames(nameToSet);
  325. MaterialPropertyValue valueToSet = action.GetArg(AZ::Name("value"));
  326. // Due to the ambiguity in the json parser (e.g. Color vs Vector[3-4]): try to cast
  327. // the value into the correct type. This also checks that the property is actually
  328. // known.
  329. if (!propertyHelper.CastToExpectedType(nameToSet, valueToSet, onError))
  330. {
  331. return false;
  332. }
  333. // Check if property already exists, in which case we overwrite its value (and warn the user)
  334. bool propertyFound = false;
  335. for (auto& [name, value] : rawProperties)
  336. {
  337. if (name == nameToSet)
  338. {
  339. value = valueToSet;
  340. AZ_Warning(
  341. "MaterialVersionUpdate", false,
  342. "SetValue operation of update to version %u has detected (and overwritten) a previous value for %s.",
  343. GetVersion(), propertyHelper.FriendlyPropertyName(nameFromSetValueAction, nameToSet).c_str());
  344. AZ_Warning("MaterialVersionUpdate", !propertyFound, "Found property %s more than once!", name.GetCStr());
  345. propertyFound = true;
  346. }
  347. }
  348. if (!propertyFound)
  349. {
  350. // Property did not exist yet, add it explicitly
  351. rawProperties.push_back({ nameToSet, valueToSet });
  352. }
  353. valueWasSet = true;
  354. }
  355. return valueWasSet;
  356. }
  357. MaterialVersionUpdate::PropertyHelper MaterialVersionUpdates::MakePropertyHelper(
  358. const MaterialPropertiesLayout* materialPropertiesLayout) const
  359. {
  360. return MaterialVersionUpdate::PropertyHelper(
  361. [this](AZ::Name& propertyId){ return ApplyPropertyRenames(propertyId); },
  362. materialPropertiesLayout);
  363. }
  364. bool MaterialVersionUpdates::ApplyVersionUpdates(
  365. MaterialAsset& materialAsset,
  366. AZStd::function<void(const char*)> reportError) const
  367. {
  368. // Validate all actions before we begin
  369. if (!ValidateUpdates(
  370. materialAsset.GetMaterialTypeAsset()->GetVersion(),
  371. materialAsset.GetMaterialPropertiesLayout(), reportError))
  372. {
  373. return false;
  374. }
  375. bool changesWereApplied = false;
  376. // Apply all renames first, so that the properties names are up
  377. // to date for the other updates actions (e.g. setValue).
  378. for (auto versionUpdate : m_versionUpdates)
  379. {
  380. // Handle rename
  381. // Note: we can perform rename updates 'blindly' (i.e. even if m_materialTypeAsset ==
  382. // UnspecifiedMaterialTypeVersion) without potential conflicts: we determine which
  383. // updates to apply by simply checking the property name, and not allowing the
  384. // same name to ever be used for two different properties (see ValidateUpdates()).
  385. for (auto& [name, value] : materialAsset.m_rawPropertyValues)
  386. {
  387. changesWereApplied |= versionUpdate.ApplyPropertyRenames(name);
  388. }
  389. }
  390. // We can handle setValue actions *only* if the material type version of the material asset is known!
  391. if (materialAsset.m_materialTypeVersion != MaterialAsset::UnspecifiedMaterialTypeVersion)
  392. {
  393. MaterialVersionUpdate::PropertyHelper propertyHelper = MakePropertyHelper(
  394. materialAsset.GetMaterialPropertiesLayout());
  395. for (auto versionUpdate : m_versionUpdates)
  396. {
  397. if (materialAsset.m_materialTypeVersion >= versionUpdate.GetVersion())
  398. {
  399. continue; // These updates are outdated and thus not needed
  400. }
  401. changesWereApplied |= versionUpdate.ApplySetValues(
  402. materialAsset.m_rawPropertyValues, propertyHelper, reportError);
  403. }
  404. }
  405. // Update the material asset's associated materialTypeVersion
  406. if (!m_versionUpdates.empty())
  407. {
  408. materialAsset.m_materialTypeVersion = m_versionUpdates.back().GetVersion();
  409. }
  410. return changesWereApplied;
  411. }
  412. const AZ::RPI::MaterialVersionUpdate::Actions& MaterialVersionUpdate::GetActions() const
  413. {
  414. return m_actions;
  415. }
  416. void MaterialVersionUpdate::AddAction(
  417. const Action& action,
  418. AZStd::function<MaterialPropertyValue(const Name&, const MaterialPropertyValue&)> sourceDataResolver)
  419. {
  420. Action resolvedAction(action);
  421. if (action.Validate() && sourceDataResolver != nullptr)
  422. {
  423. if (action.GetOperation() == AZ::Name("setValue"))
  424. {
  425. const AZ::Name& nameToSet = action.GetArgAsName(AZ::Name("name"));
  426. MaterialPropertyValue valueToSet = action.GetArg(AZ::Name("value"));
  427. // Resolve the value and overwrite it in resolvedAction:
  428. resolvedAction.AddArg(AZ::Name("value"), sourceDataResolver(nameToSet, valueToSet));
  429. AZ_Assert(resolvedAction.Validate(), "Resolving value led to invalid action");
  430. }
  431. }
  432. m_actions.push_back(resolvedAction);
  433. }
  434. bool MaterialVersionUpdates::ApplyPropertyRenames(AZ::Name& propertyId) const
  435. {
  436. bool renamed = false;
  437. for (auto versionUpdate : m_versionUpdates)
  438. {
  439. renamed |= versionUpdate.ApplyPropertyRenames(propertyId);
  440. }
  441. return renamed;
  442. }
  443. MaterialVersionUpdate::Action::Action(const ActionDefinition& fullActionDefinition)
  444. {
  445. for (auto& [key, value] : fullActionDefinition)
  446. {
  447. if (key == "op")
  448. {
  449. if (value.Is<AZStd::string>())
  450. {
  451. m_operation = value.GetValue<AZStd::string>();
  452. }
  453. else
  454. {
  455. AZ_Error("MaterialVersionUpdate", false, "The operation type under the 'op' key should be a string");
  456. return;
  457. }
  458. }
  459. else
  460. {
  461. AddArg(AZ::Name(key), value);
  462. }
  463. }
  464. // Verify that we got an "op" key for our operation type
  465. if (m_operation.IsEmpty())
  466. {
  467. AZ_Error("MaterialVersionUpdate", false, "The operation type under the 'op' key was missing or empty");
  468. }
  469. }
  470. MaterialVersionUpdate::Action::Action(
  471. const AZStd::initializer_list<AZStd::pair<AZStd::string, MaterialPropertyValue>>& fullActionDefinition)
  472. : MaterialVersionUpdate::Action::Action(ActionDefinition(fullActionDefinition))
  473. {
  474. }
  475. MaterialVersionUpdate::Action::Action(
  476. const AZ::Name& operation,
  477. const AZStd::initializer_list<AZStd::pair<AZ::Name, MaterialPropertyValue>>& args)
  478. : m_operation(operation)
  479. {
  480. for (const auto& arg : args)
  481. {
  482. AddArg(arg.first, arg.second);
  483. }
  484. }
  485. size_t MaterialVersionUpdate::Action::GetArgCount() const
  486. {
  487. return m_argsMap.size();
  488. }
  489. void MaterialVersionUpdate::Action::AddArg(const AZ::Name& key, const MaterialPropertyValue& argument)
  490. {
  491. m_argsMap[key] = MaterialPropertyValueWrapper(argument);
  492. }
  493. const MaterialPropertyValue& MaterialVersionUpdate::Action::GetArg(const AZ::Name& key) const
  494. {
  495. const auto it = m_argsMap.find(key);
  496. if (it == m_argsMap.end())
  497. {
  498. return s_invalidValue;
  499. }
  500. return it->second.Get();
  501. }
  502. const AZ::Name& MaterialVersionUpdate::Action::GetArgAsName(const AZ::Name& key) const
  503. {
  504. const auto it = m_argsMap.find(key);
  505. if (it == m_argsMap.end())
  506. {
  507. return MaterialPropertyValueWrapper::s_invalidName;
  508. }
  509. return it->second.GetAsName();
  510. }
  511. const AZ::Name& MaterialVersionUpdate::Action::GetOperation() const
  512. {
  513. return m_operation;
  514. }
  515. bool MaterialVersionUpdate::Action::operator==(const Action& other) const
  516. {
  517. return m_argsMap == other.m_argsMap;
  518. }
  519. bool MaterialVersionUpdates::ValidateUpdates(
  520. uint32_t materialTypeVersion, const MaterialPropertiesLayout* materialPropertiesLayout,
  521. AZStd::function<void(const char*)> onError) const
  522. {
  523. if (m_versionUpdates.empty())
  524. {
  525. return true;
  526. }
  527. uint32_t prevVersion = 0;
  528. // Do an initial 'light' validation pass without a propertyHelper
  529. // to check basic consistency (e.g. check rename actions).
  530. for (const MaterialVersionUpdate& versionUpdate : m_versionUpdates)
  531. {
  532. if (!versionUpdate.ValidateActions(nullptr, onError))
  533. {
  534. return false;
  535. }
  536. }
  537. // We succeeded in our 'light' validation, make a PropertyHelper that
  538. // points back to us for the 'full' validation.
  539. MaterialVersionUpdate::PropertyHelper propertyHelper = MakePropertyHelper(materialPropertiesLayout);
  540. AZStd::unordered_set<AZ::Name> renamedPropertyNewNames; // Collect final names of any renamed properties
  541. for (const MaterialVersionUpdate& versionUpdate : m_versionUpdates)
  542. {
  543. // Validate internal consistency, 'full' version with propertyHelper
  544. if (!versionUpdate.ValidateActions(&propertyHelper, onError))
  545. {
  546. return false;
  547. }
  548. if (versionUpdate.GetVersion() <= prevVersion)
  549. {
  550. onError(AZStd::string::format(
  551. "Version updates are not sequential. See version update '%u'.",
  552. versionUpdate.GetVersion()).c_str());
  553. return false;
  554. }
  555. if (versionUpdate.GetVersion() > materialTypeVersion)
  556. {
  557. onError(AZStd::string::format(
  558. "Version updates go beyond the current material type version. See version update '%u'.",
  559. versionUpdate.GetVersion()).c_str());
  560. return false;
  561. }
  562. // We don't allow previously renamed property names to be reused for new properties.
  563. // This would just complicate too many things, as every use of every property name
  564. // (like in Material Component, or in scripts, for example) would have to have a
  565. // version number associated with it, in order to know whether or which rename to apply.
  566. for (size_t propertyIndex = 0; propertyIndex < materialPropertiesLayout->GetPropertyCount(); ++propertyIndex)
  567. {
  568. MaterialPropertyIndex idx(propertyIndex);
  569. Name originalPropertyName = materialPropertiesLayout->GetPropertyDescriptor(idx)->GetName();
  570. Name newPropertyName = originalPropertyName;
  571. if (versionUpdate.ApplyPropertyRenames(newPropertyName))
  572. {
  573. onError(AZStd::string::format(
  574. "There was a material property named '%s' at material type version %u. "
  575. "This name cannot be reused for another property.",
  576. originalPropertyName.GetCStr(), versionUpdate.GetVersion())
  577. .c_str());
  578. return false;
  579. }
  580. }
  581. // Collect any rename 'endpoints'
  582. for (const auto& action : versionUpdate.GetActions())
  583. {
  584. if (action.GetOperation() != AZ::Name("rename"))
  585. {
  586. continue;
  587. }
  588. // if we come from a name that was renamed previously: remove that previous new name
  589. const Name& from = action.GetArgAsName(AZ::Name{ "from" });
  590. renamedPropertyNewNames.erase(from);
  591. // and keep track of the new name
  592. const Name& to = action.GetArgAsName(AZ::Name{ "to" });
  593. renamedPropertyNewNames.emplace(to);
  594. }
  595. prevVersion = versionUpdate.GetVersion();
  596. }
  597. // Verify that we indeed have all new names.
  598. for (const auto& propertyName : renamedPropertyNewNames)
  599. {
  600. const auto propertyIndex = materialPropertiesLayout->FindPropertyIndex(AZ::Name{ propertyName });
  601. if (!propertyIndex.IsValid())
  602. {
  603. onError(AZStd::string::format(
  604. "Renamed property '%s' not found in material property layout. "
  605. "Check that the property name has been upgraded to the correct version",
  606. propertyName.GetCStr()).c_str());
  607. return false;
  608. }
  609. }
  610. return true;
  611. }
  612. void MaterialVersionUpdates::AddVersionUpdate(
  613. const MaterialVersionUpdate& versionUpdate)
  614. {
  615. m_versionUpdates.push_back(versionUpdate);
  616. }
  617. size_t MaterialVersionUpdates::GetVersionUpdateCount() const
  618. {
  619. return m_versionUpdates.size();
  620. }
  621. const MaterialVersionUpdate& MaterialVersionUpdates::GetVersionUpdate(size_t i) const
  622. {
  623. return m_versionUpdates[i];
  624. }
  625. } // namespace RPI
  626. } // namespace AZ