3
0

AtomToolsAnyDocument.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  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.Edit/Common/AssetUtils.h>
  9. #include <Atom/RPI.Edit/Common/JsonUtils.h>
  10. #include <Atom/RPI.Reflect/System/AnyAsset.h>
  11. #include <AtomToolsFramework/Document/AtomToolsAnyDocument.h>
  12. #include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
  13. #include <AtomToolsFramework/Util/Util.h>
  14. #include <AzCore/IO/ByteContainerStream.h>
  15. #include <AzCore/RTTI/BehaviorContext.h>
  16. #include <AzCore/RTTI/RTTI.h>
  17. #include <AzCore/Serialization/EditContext.h>
  18. #include <AzCore/Serialization/ObjectStream.h>
  19. #include <AzCore/Serialization/SerializeContext.h>
  20. #include <AzCore/Serialization/Utils.h>
  21. #include <AzCore/Utils/Utils.h>
  22. namespace AtomToolsFramework
  23. {
  24. void AtomToolsAnyDocument::Reflect(AZ::ReflectContext* context)
  25. {
  26. if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
  27. {
  28. serialize->Class<AtomToolsAnyDocument, AtomToolsDocument>()->Version(0);
  29. }
  30. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  31. {
  32. behaviorContext->EBus<AtomToolsAnyDocumentRequestBus>("AtomToolsAnyDocumentRequestBus")
  33. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  34. ->Attribute(AZ::Script::Attributes::Category, "Editor")
  35. ->Attribute(AZ::Script::Attributes::Module, "atomtools")
  36. ->Event("GetContent", &AtomToolsAnyDocumentRequests::GetContent);
  37. }
  38. }
  39. AtomToolsAnyDocument::AtomToolsAnyDocument(
  40. const AZ::Crc32& toolId,
  41. const DocumentTypeInfo& documentTypeInfo,
  42. const AZStd::any& defaultValue,
  43. const AZ::Uuid& contentTypeIdIfNotEmbedded)
  44. : AtomToolsDocument(toolId, documentTypeInfo)
  45. , m_content(defaultValue)
  46. , m_contentTypeIdIfNotEmbedded(contentTypeIdIfNotEmbedded)
  47. {
  48. AtomToolsAnyDocumentRequestBus::Handler::BusConnect(m_id);
  49. }
  50. AtomToolsAnyDocument::~AtomToolsAnyDocument()
  51. {
  52. AtomToolsAnyDocumentRequestBus::Handler::BusDisconnect();
  53. }
  54. DocumentTypeInfo AtomToolsAnyDocument::BuildDocumentTypeInfo(
  55. const AZStd::string& documentTypeName,
  56. const AZStd::vector<AZStd::string>& documentTypeExtensions,
  57. const AZStd::vector<AZStd::string>& documentTypeTemplateExtensions,
  58. const AZStd::any& defaultValue,
  59. const AZ::Uuid& contentTypeIdIfNotEmbedded)
  60. {
  61. DocumentTypeInfo documentType;
  62. documentType.m_documentTypeName = documentTypeName;
  63. documentType.m_documentFactoryCallback =
  64. [defaultValue, contentTypeIdIfNotEmbedded](const AZ::Crc32& toolId, const DocumentTypeInfo& documentTypeInfo)
  65. {
  66. return aznew AtomToolsAnyDocument(toolId, documentTypeInfo, defaultValue, contentTypeIdIfNotEmbedded);
  67. };
  68. for (const auto& extension : documentTypeExtensions)
  69. {
  70. documentType.m_supportedExtensionsToOpen.push_back({ documentTypeName, extension });
  71. documentType.m_supportedExtensionsToSave.push_back({ documentTypeName, extension });
  72. }
  73. for (const auto& extension : documentTypeTemplateExtensions)
  74. {
  75. documentType.m_supportedExtensionsToCreate.push_back({ documentTypeName + " Template", extension });
  76. }
  77. return documentType;
  78. }
  79. DocumentObjectInfoVector AtomToolsAnyDocument::GetObjectInfo() const
  80. {
  81. DocumentObjectInfoVector objects = AtomToolsDocument::GetObjectInfo();
  82. if (!m_content.empty())
  83. {
  84. // The reflected data stored within the document will be converted to a description of the object and its type info. This data
  85. // will be used to populate the inspector.
  86. DocumentObjectInfo objectInfo;
  87. objectInfo.m_visible = true;
  88. objectInfo.m_name = GetDocumentTypeInfo().m_documentTypeName;
  89. objectInfo.m_displayName = GetDocumentTypeInfo().m_documentTypeName;
  90. objectInfo.m_description = GetDocumentTypeInfo().m_documentTypeName;
  91. objectInfo.m_objectType = m_content.type();
  92. objectInfo.m_objectPtr = AZStd::any_cast<void>(const_cast<AZStd::any*>(&m_content));
  93. objects.push_back(AZStd::move(objectInfo));
  94. }
  95. return objects;
  96. }
  97. bool AtomToolsAnyDocument::Open(const AZStd::string& loadPath)
  98. {
  99. if (!AtomToolsDocument::Open(loadPath))
  100. {
  101. return false;
  102. }
  103. if (!LoadAny())
  104. {
  105. return OpenFailed();
  106. }
  107. m_modified = false;
  108. return OpenSucceeded();
  109. }
  110. bool AtomToolsAnyDocument::Save()
  111. {
  112. if (!AtomToolsDocument::Save())
  113. {
  114. // SaveFailed has already been called so just forward the result without additional notifications.
  115. // TODO Replace bool return value with enum for open and save states.
  116. return false;
  117. }
  118. if (!SaveAny())
  119. {
  120. return SaveFailed();
  121. }
  122. m_modified = false;
  123. m_absolutePath = m_savePathNormalized;
  124. return SaveSucceeded();
  125. }
  126. bool AtomToolsAnyDocument::SaveAsCopy(const AZStd::string& savePath)
  127. {
  128. if (!AtomToolsDocument::SaveAsCopy(savePath))
  129. {
  130. // SaveFailed has already been called so just forward the result without additional notifications.
  131. // TODO Replace bool return value with enum for open and save states.
  132. return false;
  133. }
  134. if (!SaveAny())
  135. {
  136. return SaveFailed();
  137. }
  138. m_modified = false;
  139. m_absolutePath = m_savePathNormalized;
  140. return SaveSucceeded();
  141. }
  142. bool AtomToolsAnyDocument::SaveAsChild(const AZStd::string& savePath)
  143. {
  144. if (!AtomToolsDocument::SaveAsChild(savePath))
  145. {
  146. // SaveFailed has already been called so just forward the result without additional notifications.
  147. // TODO Replace bool return value with enum for open and save states.
  148. return false;
  149. }
  150. if (!SaveAny())
  151. {
  152. return SaveFailed();
  153. }
  154. m_modified = false;
  155. m_absolutePath = m_savePathNormalized;
  156. return SaveSucceeded();
  157. }
  158. bool AtomToolsAnyDocument::IsModified() const
  159. {
  160. return m_modified;
  161. }
  162. bool AtomToolsAnyDocument::BeginEdit()
  163. {
  164. RecordContentState();
  165. return true;
  166. }
  167. bool AtomToolsAnyDocument::EndEdit()
  168. {
  169. auto undoState = m_contentStateForUndoRedo;
  170. RecordContentState();
  171. auto redoState = m_contentStateForUndoRedo;
  172. if (undoState != redoState)
  173. {
  174. AddUndoRedoHistory(
  175. [this, undoState]() { RestoreContentState(undoState); },
  176. [this, redoState]() { RestoreContentState(redoState); });
  177. m_modified = true;
  178. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
  179. }
  180. return true;
  181. }
  182. void AtomToolsAnyDocument::Clear()
  183. {
  184. m_contentStateForUndoRedo.clear();
  185. m_content.clear();
  186. m_modified = false;
  187. AtomToolsDocument::Clear();
  188. }
  189. const AZStd::any& AtomToolsAnyDocument::GetContent() const
  190. {
  191. return m_content;
  192. }
  193. void AtomToolsAnyDocument::RecordContentState()
  194. {
  195. // Serialize the current content to a byte stream so that it can be restored with undo redo operations.
  196. m_contentStateForUndoRedo.clear();
  197. AZ::IO::ByteContainerStream<decltype(m_contentStateForUndoRedo)> undoContentStateStream(&m_contentStateForUndoRedo);
  198. AZ::Utils::SaveObjectToStream(undoContentStateStream, AZ::ObjectStream::ST_BINARY, &m_content);
  199. }
  200. void AtomToolsAnyDocument::RestoreContentState(const AZStd::vector<AZ::u8>& contentState)
  201. {
  202. // Restore a version of the content that was previously serialized to a byte stream
  203. m_contentStateForUndoRedo = contentState;
  204. AZ::IO::ByteContainerStream<decltype(m_contentStateForUndoRedo)> undoContentStateStream(&m_contentStateForUndoRedo);
  205. m_content.clear();
  206. AZ::Utils::LoadObjectFromStreamInPlace(undoContentStateStream, m_content);
  207. m_modified = true;
  208. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentObjectInfoInvalidated, m_id);
  209. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
  210. }
  211. bool AtomToolsAnyDocument::LoadAny()
  212. {
  213. m_content.clear();
  214. // When this type ID is provided an attempt is made to load the data from the JSON file, assuming that the file only contains
  215. // reflected object data.
  216. if (!m_contentTypeIdIfNotEmbedded.IsNull())
  217. {
  218. // Serialized context is required to create a placeholder object using the type ID.
  219. AZ::SerializeContext* serializeContext = {};
  220. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  221. AZ_Assert(serializeContext, "Failed to acquire application serialize context.");
  222. m_content = serializeContext->CreateAny(m_contentTypeIdIfNotEmbedded);
  223. if (m_content.empty())
  224. {
  225. AZ_Error(
  226. "AtomToolsAnyDocument",
  227. false,
  228. "Failed to create AZStd::any from type: %s",
  229. m_contentTypeIdIfNotEmbedded.ToFixedString().c_str());
  230. return false;
  231. }
  232. // Attempt to read the JSON file data from the document breath.
  233. auto loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(m_absolutePath);
  234. if (!loadOutcome.IsSuccess())
  235. {
  236. AZ_Error("AtomToolsAnyDocument", false, "Failed to read JSON file: %s", loadOutcome.GetError().c_str());
  237. return false;
  238. }
  239. // Read the rapid JSON document data into the object we just created.
  240. AZ::JsonDeserializerSettings jsonSettings;
  241. AZ::RPI::JsonReportingHelper reportingHelper;
  242. reportingHelper.Attach(jsonSettings);
  243. rapidjson::Document& document = loadOutcome.GetValue();
  244. AZ::JsonSerialization::Load(AZStd::any_cast<void>(&m_content), m_contentTypeIdIfNotEmbedded, document, jsonSettings);
  245. if (reportingHelper.ErrorsReported())
  246. {
  247. AZ_Error("AtomToolsAnyDocument", false, "Failed to load object from JSON file: %s", m_absolutePath.c_str());
  248. return false;
  249. }
  250. }
  251. else
  252. {
  253. // If no time ID was provided the serializer will attempt to parse it from the JSON file.
  254. auto loadResult = AZ::JsonSerializationUtils::LoadAnyObjectFromFile(m_absolutePath);
  255. if (!loadResult)
  256. {
  257. AZ_Error("AtomToolsAnyDocument", false, "Failed to load object from JSON file: %s", m_absolutePath.c_str());
  258. return false;
  259. }
  260. m_content = loadResult.GetValue();
  261. }
  262. return true;
  263. }
  264. bool AtomToolsAnyDocument::SaveAny() const
  265. {
  266. if (m_content.empty())
  267. {
  268. return false;
  269. }
  270. if (!m_contentTypeIdIfNotEmbedded.IsNull())
  271. {
  272. // Create a default content object of the specified type that will be used to keep the serializer from writing out unmodified
  273. // values.
  274. AZ::SerializeContext* serializeContext = {};
  275. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  276. AZ_Assert(serializeContext, "Failed to acquire application serialize context.");
  277. AZStd::any defaultContent = serializeContext->CreateAny(m_contentTypeIdIfNotEmbedded);
  278. if (defaultContent.empty())
  279. {
  280. AZ_Error(
  281. "AtomToolsAnyDocument",
  282. false,
  283. "Failed to create AZStd::any from type: %s",
  284. m_contentTypeIdIfNotEmbedded.ToFixedString().c_str());
  285. return false;
  286. }
  287. // Create a rapid JSON document and object to serialize the document data into.
  288. AZ::JsonSerializerSettings settings;
  289. AZ::RPI::JsonReportingHelper reportingHelper;
  290. reportingHelper.Attach(settings);
  291. rapidjson::Document document;
  292. document.SetObject();
  293. AZ::JsonSerialization::Store(
  294. document,
  295. document.GetAllocator(),
  296. AZStd::any_cast<void>(&m_content),
  297. AZStd::any_cast<void>(&defaultContent),
  298. m_contentTypeIdIfNotEmbedded,
  299. settings);
  300. if (reportingHelper.ErrorsReported())
  301. {
  302. AZ_Error("AtomToolsAnyDocument", false, "Failed to write object data to JSON document: %s", m_savePathNormalized.c_str());
  303. return false;
  304. }
  305. AZ::JsonSerializationUtils::WriteJsonFile(document, m_savePathNormalized);
  306. if (reportingHelper.ErrorsReported())
  307. {
  308. AZ_Error("AtomToolsAnyDocument", false, "Failed to write JSON document to file: %s", m_savePathNormalized.c_str());
  309. return false;
  310. }
  311. }
  312. else
  313. {
  314. if (!AZ::JsonSerializationUtils::SaveObjectToFileByType(
  315. AZStd::any_cast<void>(&m_content), m_content.type(), m_savePathNormalized))
  316. {
  317. AZ_Error("AtomToolsAnyDocument", false, "Failed to write JSON document to file: %s", m_savePathNormalized.c_str());
  318. return false;
  319. }
  320. }
  321. return true;
  322. }
  323. } // namespace AtomToolsFramework