/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ScriptCanvasEditor { class ScriptCanvasAssetHandler; template class MemoryAsset : protected AZ::Data::AssetBus::MultiHandler , protected AzToolsFramework::AssetSystemBus::Handler , public AzToolsFramework::UndoSystem::IUndoNotify { public: MemoryAsset() { AzToolsFramework::AssetSystemBus::Handler::BusConnect(); } ~MemoryAsset() { AZ::Data::AssetBus::MultiHandler::BusDisconnect(); AzToolsFramework::AssetSystemBus::Handler::BusDisconnect(); } using AssetType = BaseAssetType; virtual void Create(AZ::Data::AssetId assetId, AZStd::string_view assetAbsolutePath, AZ::Data::AssetType assetType, Callbacks::OnAssetCreatedCallback onAssetCreatedCallback) = 0; virtual void SaveAs(const AZStd::string& path, Callbacks::OnSave onSaveCallback) = 0; virtual void Save(Callbacks::OnSave onSaveCallback) = 0; virtual bool Load(AZ::Data::AssetId assetId, AZ::Data::AssetType assetType, Callbacks::OnAssetReadyCallback onAssetReadyCallback) = 0; }; class UndoHelper; // Script Canvas primarily works with an in-memory copy of an asset. // There are two situations, the first is, when a new asset is created and not yet saved. // Once saved, we will create a new asset on file, however, and this is important // once the file is saved to file, its asset ID will be changed, if the file is to remain // open, we need to update the source AssetId to correspond to the file asset. // // The other is when an asset is loaded, we clone the asset from file and use an in-memory // version of the asset until it is time to save, at that moment we need to save to the // source file class ScriptCanvasMemoryAsset : public MemoryAsset , public AZStd::enable_shared_from_this , EditorGraphNotificationBus::Handler , public AZ::SystemTickBus::Handler { public: ScriptCanvasMemoryAsset(); ~ScriptCanvasMemoryAsset() override; ScriptCanvasMemoryAsset(const ScriptCanvasMemoryAsset& rhs) = delete; ScriptCanvasMemoryAsset& operator = (const ScriptCanvasMemoryAsset& rhs) = delete; using pointer = AZStd::shared_ptr; void Create(AZ::Data::AssetId assetId, AZStd::string_view assetAbsolutePath, AZ::Data::AssetType assetType, Callbacks::OnAssetCreatedCallback onAssetCreatedCallback) override; void SaveAs(const AZStd::string& path, Callbacks::OnSave onSaveCallback) override; void Save(Callbacks::OnSave onSaveCallback) override; bool Load(AZ::Data::AssetId assetId, AZ::Data::AssetType assetType, Callbacks::OnAssetReadyCallback onAssetReadyCallback) override; void Set(AZ::Data::AssetId assetId); const AZ::Data::AssetId& GetId() const { return m_inMemoryAssetId; } const AZ::Data::AssetId& GetFileAssetId() const { return m_fileAssetId; } const AZ::Data::AssetType GetAssetType() const { return m_assetType; } AZ::Data::Asset GetAsset() { return m_inMemoryAsset; } const AZ::Data::Asset& GetAsset() const { return m_inMemoryAsset; } const AZStd::string GetTabName() const; const AZStd::string& GetAbsolutePath() const { return m_absolutePath; } AZ::EntityId GetScriptCanvasId() const { return m_scriptCanvasId; } AZ::EntityId GetGraphId(); Tracker::ScriptCanvasFileState GetFileState() const; void SetFileState(Tracker::ScriptCanvasFileState fileState); void CloneTo(ScriptCanvasMemoryAsset& memoryAsset); void ActivateAsset(); Widget::CanvasWidget* GetView() { return m_canvasWidget; } Widget::CanvasWidget* CreateView(QWidget* parent); void ClearView(); void UndoStackChange(); SceneUndoState* GetUndoState() { return m_undoState.get(); } bool IsSourceInError() const; // SystemTickBus void OnSystemTick() override; //// private: template AZ::Data::Asset Clone() { AZ::Data::AssetId assetId = AZ::Uuid::CreateRandom(); AZ::Data::Asset newAsset = m_inMemoryAsset; newAsset = aznew T(assetId, AZ::Data::AssetData::AssetStatus::Ready); auto serializeContext = AZ::EntityUtils::GetApplicationSerializeContext(); serializeContext->CloneObjectInplace(newAsset.Get()->GetScriptCanvasData(), &m_inMemoryAsset.Get()->GetScriptCanvasData()); m_editorEntityIdMap.clear(); AZ::IdUtils::Remapper::GenerateNewIdsAndFixRefs(&newAsset.Get()->GetScriptCanvasData(), m_editorEntityIdMap, serializeContext); return newAsset; } // EditorGraphNotificationBus void OnGraphCanvasSceneDisplayed() override; /// //! AZ::Data::AssetBus void OnAssetReady(AZ::Data::Asset asset) override; void OnAssetReloaded(AZ::Data::Asset asset) override; void OnAssetError(AZ::Data::Asset asset) override; void OnAssetUnloaded(const AZ::Data::AssetId assetId, const AZ::Data::AssetType assetType) override; /////////////////////// // AzToolsFramework::AssetSystemBus::Handler void SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid fileAssetId) override; void SourceFileRemoved(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid fileAssetId) override; void SourceFileFailed(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid fileAssetId) override; /// void FinalizeAssetSave(bool, const AzToolsFramework::SourceControlFileInfo& fileInfo, const AZ::Data::AssetStreamInfo& saveInfo, Callbacks::OnSave onSaveCallback); template void StartAssetLoad(AZ::Data::AssetId assetId, AZ::Data::Asset& asset) { asset = AZ::Data::AssetManager::Instance().GetAsset(assetId, true, nullptr, false); } template AZ::Data::Asset CloneAssetData(AZ::Data::AssetId newAssetId) { AssetType* assetData = aznew AssetType(newAssetId, AZ::Data::AssetData::AssetStatus::Ready); auto& scriptCanvasData = assetData->GetScriptCanvasData(); // Clone asset data into SC Editor asset auto serializeContext = AZ::EntityUtils::GetApplicationSerializeContext(); serializeContext->CloneObjectInplace(scriptCanvasData, &m_inMemoryAsset.Get()->GetScriptCanvasData()); m_editorEntityIdMap.clear(); AZ::IdUtils::Remapper::GenerateNewIdsAndFixRefs(&scriptCanvasData, m_editorEntityIdMap, serializeContext); // Upon doing this move, the canonical asset will be unloaded m_inMemoryAsset = AZStd::move(assetData); return m_inMemoryAsset; } // Upon loading a graph, we clone the source data and we replace the loaded asset with // a clone, this is to prevent modifications to the source data and it gives us some // flexibility if we need to load the source asset again AZ::Data::Asset CloneAssetData(AZ::Data::AssetId newAssetId); // IUndoNotify void OnUndoStackChanged() override; // private: void SignalFileStateChanged(); AZStd::string MakeTemporaryFilePathForSave(AZStd::string_view targetFilename); //! Finds the appropriate asset handler for the type of Script Canvas asset given ScriptCanvasAssetHandler* GetAssetHandlerForType(AZ::Data::AssetType assetType); //! The asset type, we need it to make sure we call the correct factory methods AZ::Data::AssetType m_assetType; //! The in-memory asset AZ::Data::Asset m_inMemoryAsset; AZ::Data::Asset m_sourceAsset; //! Whether we are making a new asset or loading one, we should always have its absolute path AZStd::string m_absolutePath; AZStd::string m_saveAsPath; //! The AssetId of the canonical asset on file, if the asset has never been saved to file, it is Invalid AZ::Data::AssetId m_fileAssetId; //! The AssetId that represents this asset, it will always be the in-memory asset Id and never the file asset Id AZ::Data::AssetId m_inMemoryAssetId; //! When a new asset is saved, we need to keep it's previous internal Ids in order for the front end to remap to the new Ids using FormerGraphIdPair = AZStd::pair; FormerGraphIdPair m_formerGraphIdPair; //! The EntityId of the ScriptCanvasEntity owned by the ScriptCanvasAsset ScriptCanvas::ScriptCanvasId m_scriptCanvasId; //! The EntityId that represents the ScriptCanvas graph AZ::EntityId m_graphId; //! Gives the ability to provide a lambda invoked when the asset is ready Callbacks::OnAssetReadyCallback m_onAssetReadyCallback; //! The Save is officially complete after SourceFileChange is handled. Callbacks::OnSave m_onSaveCallback; bool m_sourceRemoved = false; Tracker::ScriptCanvasFileState m_fileState = Tracker::ScriptCanvasFileState::INVALID; //! We need to track the filename of the file being saved because we need to match it when we handle SourceFileChange (see SourceFileChange for details) AZStd::vector m_pendingSave; //! Each memory asset owns its view widget Widget::CanvasWidget* m_canvasWidget = nullptr; //! Utility cache of remapped entityIds using EditorEntityIdMap = AZStd::unordered_map; //! Cached mapping of Scene Entity ID to Asset Id, used by the debugger EditorEntityIdMap m_editorEntityIdMap; //! Each asset keeps track of its undo state AZStd::unique_ptr m_undoState; //! The undo helper is an object that implements the Undo behaviors AZStd::unique_ptr m_undoHelper; bool m_sourceInError; bool m_triggerErrorSaveCallback; // Callback flag to avoid the SourceFileChange callback, since there are situations // where that won't be called and we still need to complete our save loop bool m_triggerSourceChangedFromTickBus = false; AZStd::string m_relativePath; AZStd::string m_scanFolder; AZ::Data::AssetId m_sourceUuid; public: //! Given a scene EntityId, find the respective editor EntityId AZ::EntityId GetEditorEntityIdFromSceneEntityId(AZ::EntityId sceneEntityId); //! Given an editor EntityId, find the respective scene EntityId AZ::EntityId GetSceneEntityIdFromEditorEntityId(AZ::EntityId editorEntityId); //! When a new asset is saved, we need to keep it's previous internal Ids in order for the front end to remap to the new Ids const FormerGraphIdPair& GetFormerGraphIds() const { return m_formerGraphIdPair; } }; // Helper class that provides the implementation for UndoRequestBus class UndoHelper : UndoRequestBus::Handler { public: UndoHelper(ScriptCanvasMemoryAsset& memoryAsset); ~UndoHelper(); UndoCache* GetSceneUndoCache() override; UndoData CreateUndoData() override; void BeginUndoBatch(AZStd::string_view label) override; void EndUndoBatch() override; void AddUndo(AzToolsFramework::UndoSystem::URSequencePoint* seqPoint) override; void AddGraphItemChangeUndo(AZStd::string_view undoLabel) override; void AddGraphItemAdditionUndo(AZStd::string_view undoLabel) override; void AddGraphItemRemovalUndo(AZStd::string_view undoLabel) override; void Undo() override; void Redo() override; void Reset() override; bool IsActive() override; bool IsIdle() override; bool CanUndo() const override; bool CanRedo() const override; private: void UpdateCache(); enum class Status { Idle, InUndo }; Status m_status = Status::Idle; ScriptCanvasMemoryAsset& m_memoryAsset; }; }