/* * 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. * */ #include "precompiled.h" #include "ScriptCanvasMemoryAsset.h" #include #include #include #include #include namespace ScriptCanvasEditor { ScriptCanvasMemoryAsset::ScriptCanvasMemoryAsset() : m_sourceInError(false) , m_triggerErrorSaveCallback(false) { m_undoState = AZStd::make_unique(this); } ScriptCanvasMemoryAsset::~ScriptCanvasMemoryAsset() { AssetTrackerRequestBus::Broadcast(&AssetTrackerRequests::UntrackAsset, m_inMemoryAssetId); AssetHelpers::PrintInfo(AZStd::string::format("ScriptCanvasMemoryAsset went out of scope and has been released and untracked: %s", m_absolutePath.c_str()).c_str()); if (m_inMemoryAsset.IsReady() && !m_inMemoryAsset.Release()) { // Something else is holding on to it AZ_Assert(false, "Unable to release in memory asset"); } } const AZStd::string ScriptCanvasMemoryAsset::GetTabName() const { AZStd::string tabName; AzFramework::StringFunc::Path::GetFileName(m_absolutePath.c_str(), tabName); return tabName; } AZ::EntityId ScriptCanvasMemoryAsset::GetGraphId() { if (!m_graphId.IsValid()) { EditorGraphRequestBus::EventResult(m_graphId, m_scriptCanvasId, &EditorGraphRequests::GetGraphCanvasGraphId); } return m_graphId; } Tracker::ScriptCanvasFileState ScriptCanvasMemoryAsset::GetFileState() const { if (m_sourceRemoved) { return Tracker::ScriptCanvasFileState::SOURCE_REMOVED; } else { return m_fileState; } } void ScriptCanvasMemoryAsset::SetFileState(Tracker::ScriptCanvasFileState fileState) { m_fileState = fileState; SignalFileStateChanged(); } void ScriptCanvasMemoryAsset::CloneTo(ScriptCanvasMemoryAsset& memoryAsset) { if (m_assetType == azrtti_typeid()) { AZ::Data::Asset newAsset = Clone(); memoryAsset.m_inMemoryAsset = newAsset; } else if (m_assetType == azrtti_typeid()) { AZ::Data::Asset newAsset = Clone(); memoryAsset.m_inMemoryAsset = newAsset; } else { AZ_Assert(false, "Unsupported Script Canvas Asset Type"); } memoryAsset.m_sourceAsset = m_sourceAsset; } void ScriptCanvasMemoryAsset::Create(AZ::Data::AssetId assetId, AZStd::string_view assetAbsolutePath, AZ::Data::AssetType assetType, Callbacks::OnAssetCreatedCallback onAssetCreatedCallback) { m_inMemoryAssetId = assetId; m_absolutePath = assetAbsolutePath; m_assetType = assetType; m_fileState = Tracker::ScriptCanvasFileState::NEW; ScriptCanvasAssetHandler* assetHandler = GetAssetHandlerForType(assetType); AZ::Data::Asset asset; if (assetType == azrtti_typeid()) { asset = assetHandler->CreateAsset(assetId, azrtti_typeid()); } else if (assetType == azrtti_typeid()) { asset = assetHandler->CreateAsset(assetId, azrtti_typeid()); } m_inMemoryAsset = asset; ActivateAsset(); // For new assets, we directly set its status as "Ready" in order to make it usable. ScriptCanvas::ScriptCanvasAssetBusRequestBus::Event(assetId, &ScriptCanvas::ScriptCanvasAssetBusRequests::SetAsNewAsset); Internal::MemoryAssetSystemNotificationBus::Broadcast(&Internal::MemoryAssetSystemNotifications::OnAssetReady, this); AssetHelpers::PrintInfo("Newly created Script Canvas asset is now tracked: %s", AssetHelpers::AssetIdToString(assetId).c_str()); if (onAssetCreatedCallback) { AZStd::invoke(onAssetCreatedCallback, *this); } } void ScriptCanvasMemoryAsset::Save(Callbacks::OnSave onSaveCallback) { if (m_fileState == Tracker::ScriptCanvasFileState::UNMODIFIED) { // The file hasn't changed, don't save it return; } SaveAs({}, onSaveCallback); } void ScriptCanvasMemoryAsset::SaveAs(const AZStd::string& path, Callbacks::OnSave onSaveCallback) { if (!path.empty()) { m_saveAsPath = path; } else { m_saveAsPath = m_absolutePath; } AZ::Data::AssetStreamInfo streamInfo; streamInfo.m_streamFlags = AZ::IO::OpenMode::ModeWrite; streamInfo.m_isCustomStreamType = true; streamInfo.m_streamName = m_saveAsPath; ScriptCanvas::ScriptCanvasDataRequestBus::Event(GetScriptCanvasId(), &ScriptCanvas::ScriptCanvasDataRequests::SetPrettyName, GetTabName().c_str()); if (!streamInfo.IsValid()) { return; } bool sourceControlActive = false; AzToolsFramework::SourceControlConnectionRequestBus::BroadcastResult(sourceControlActive, &AzToolsFramework::SourceControlConnectionRequests::IsActive); // If Source Control is active then use it to check out the file before saving otherwise query the file info and save only if the file is not read-only if (sourceControlActive) { AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEdit, streamInfo.m_streamName.c_str(), true, [this, streamInfo, onSaveCallback](bool success, AzToolsFramework::SourceControlFileInfo info) { FinalizeAssetSave(success, info, streamInfo, onSaveCallback); } ); } else { AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::GetFileInfo, streamInfo.m_streamName.c_str(), [this, streamInfo, onSaveCallback](bool success, AzToolsFramework::SourceControlFileInfo info) { FinalizeAssetSave(success, info, streamInfo, onSaveCallback); } ); } } void ScriptCanvasMemoryAsset::Set(AZ::Data::AssetId fileAssetId) { Callbacks::OnAssetReadyCallback onAssetReady = [](ScriptCanvasMemoryAsset&) {}; Load(fileAssetId, AZ::Data::AssetType::CreateNull(), onAssetReady); ActivateAsset(); } bool ScriptCanvasMemoryAsset::Load(AZ::Data::AssetId assetId, AZ::Data::AssetType assetType, Callbacks::OnAssetReadyCallback onAssetReadyCallback) { AZStd::string rootPath; AZ::Data::AssetInfo assetInfo = AssetHelpers::GetAssetInfo(assetId, rootPath); AzFramework::StringFunc::Path::Join(rootPath.c_str(), assetInfo.m_relativePath.c_str(), m_absolutePath); if (assetInfo.m_assetType.IsNull()) { // Try to find the asset type from the source file asset assetInfo.m_assetType = AssetHelpers::GetAssetType(AZStd::string::format("%s/%s", rootPath.c_str(), assetInfo.m_relativePath.c_str()).c_str()); } if (!assetType.IsNull() && assetInfo.m_assetType.IsNull()) { assetInfo.m_assetType = assetType; } else { AZ_Assert(assetInfo.m_assetId.IsValid(), "Failed to get the asset info properly from the asset system"); } m_fileAssetId = assetId; auto asset = AZ::Data::AssetManager::Instance().FindAsset(assetId); if (!asset || !asset.IsReady()) { AZ::Data::AssetBus::MultiHandler::BusConnect(assetId); } if (assetInfo.m_assetType == azrtti_typeid()) { m_inMemoryAsset = AZ::Data::AssetManager::Instance().GetAsset(assetId, true, nullptr, false); } else if (assetInfo.m_assetType == azrtti_typeid()) { m_inMemoryAsset = AZ::Data::AssetManager::Instance().GetAsset(assetId, true, nullptr, false); } m_sourceAsset = m_inMemoryAsset; m_assetType = m_inMemoryAsset.GetType(); AZ_Assert(m_inMemoryAsset.GetId() == assetId, "The asset IDs must match"); m_onAssetReadyCallback = onAssetReadyCallback; if (m_inMemoryAsset.IsReady()) { OnAssetReady(m_inMemoryAsset); } return !m_inMemoryAsset.IsError(); } void ScriptCanvasMemoryAsset::ActivateAsset() { ScriptCanvas::ScriptCanvasAssetBase* assetData = m_inMemoryAsset.Get(); AZ_Assert(assetData, "ActivateAsset should have a valid asset of type %s", AssetHelpers::AssetIdToString(azrtti_typeid()).c_str()); if (assetData == nullptr) { return; } AZ::Entity* scriptCanvasEntity = assetData->GetScriptCanvasEntity(); AZ_Assert(scriptCanvasEntity, "ActivateAsset should have a valid ScriptCanvas Entity"); // Only activate the entity for assets that have been saved if (scriptCanvasEntity->GetState() == AZ::Entity::ES_CONSTRUCTED) { scriptCanvasEntity->Init(); } if (scriptCanvasEntity->GetState() == AZ::Entity::ES_INIT) { scriptCanvasEntity->Activate(); } const AZStd::string& assetPath = m_absolutePath; AZStd::string graphName; AzFramework::StringFunc::Path::GetFileName(assetPath.c_str(), graphName); if (!graphName.empty()) { scriptCanvasEntity->SetName(graphName); } Graph* editorGraph = AZ::EntityUtils::FindFirstDerivedComponent(scriptCanvasEntity); AZ_Assert(editorGraph, "Script Canvas entity must have a Graph component"); if (editorGraph == nullptr) { return; } m_scriptCanvasId = editorGraph->GetScriptCanvasId(); ScriptCanvas::ScriptCanvasDataRequestBus::Event(m_scriptCanvasId, &ScriptCanvas::ScriptCanvasDataRequests::SetPrettyName, GetTabName().c_str()); EditorGraphNotificationBus::Handler::BusDisconnect(); EditorGraphNotificationBus::Handler::BusConnect(m_scriptCanvasId); m_undoHelper = AZStd::make_unique(*this); } ScriptCanvasEditor::Widget::CanvasWidget* ScriptCanvasMemoryAsset::CreateView(QWidget* parent) { m_canvasWidget = new Widget::CanvasWidget(m_fileAssetId, parent); return m_canvasWidget; } void ScriptCanvasMemoryAsset::ClearView() { delete m_canvasWidget; m_canvasWidget = nullptr; } void ScriptCanvasMemoryAsset::UndoStackChange() { OnUndoStackChanged(); } bool ScriptCanvasMemoryAsset::IsSourceInError() const { return m_sourceInError; } void ScriptCanvasMemoryAsset::OnSystemTick() { if (m_triggerErrorSaveCallback && m_onSaveCallback) { AZ::SystemTickBus::Handler::BusDisconnect(); m_onSaveCallback(false, m_inMemoryAsset, AZ::Data::AssetId()); m_triggerErrorSaveCallback = false; } else if (m_triggerSourceChangedFromTickBus && m_onSaveCallback) { SourceFileChanged(m_relativePath, m_scanFolder, m_sourceUuid.m_guid); AZ::SystemTickBus::Handler::BusDisconnect(); m_triggerSourceChangedFromTickBus = false; } } void ScriptCanvasMemoryAsset::OnGraphCanvasSceneDisplayed() { // We need to wait until this event in order the get the m_graphId which represents the GraphCanvas scene Id EditorGraphRequestBus::EventResult(m_graphId, m_scriptCanvasId, &EditorGraphRequests::GetGraphCanvasGraphId); EditorGraphNotificationBus::Handler::BusDisconnect(); } void ScriptCanvasMemoryAsset::OnAssetReady(AZ::Data::Asset asset) { // If we've already cloned the memory asset, we don't want to do the start-up things again. if (m_inMemoryAsset->GetId() == m_sourceAsset.GetId()) { AZStd::string rootPath; AZ::Data::AssetInfo assetInfo = AssetHelpers::GetAssetInfo(m_fileAssetId, rootPath); AZStd::string absolutePath; AzFramework::StringFunc::Path::Join(rootPath.c_str(), assetInfo.m_relativePath.c_str(), absolutePath); m_absolutePath = absolutePath; m_fileState = Tracker::ScriptCanvasFileState::UNMODIFIED; m_assetType = asset.GetType(); // Keep the canonical asset's Id, we will need it when we want to save the asset back to file m_fileAssetId = asset.GetId(); // The source file is ready, we need to make the an in-memory version of it. AZ::Data::AssetId inMemoryAssetId = AZ::Uuid::CreateRandom(); m_sourceAsset = AZ::Data::AssetManager::Instance().FindAsset(m_fileAssetId); m_inMemoryAsset = AZStd::move(CloneAssetData(inMemoryAssetId)); AZ_Assert(m_inMemoryAsset, "Asset should have been successfully cloned."); AZ_Assert(m_inMemoryAsset.GetId() == inMemoryAssetId, "Asset Id should match to the newly created one"); m_inMemoryAssetId = m_inMemoryAsset.GetId(); ActivateAsset(); if (m_onAssetReadyCallback) { AZStd::invoke(m_onAssetReadyCallback, *this); } } // Instead just update the source asset to the get the new asset to keep it in memory. else { m_sourceAsset = AZ::Data::AssetManager::Instance().FindAsset(m_fileAssetId); } if (m_fileAssetId == asset.GetId()) { Internal::MemoryAssetSystemNotificationBus::Broadcast(&Internal::MemoryAssetSystemNotifications::OnAssetReady, this); } } void ScriptCanvasMemoryAsset::OnAssetReloaded(AZ::Data::Asset asset) { if (m_fileAssetId == asset.GetId()) { // Update our source asset information so we keep references alive. m_sourceAsset = AZ::Data::AssetManager::Instance().FindAsset(m_fileAssetId); // The source file was reloaded, but we have an in-memory version of it. // We need to handle this. } else { AZ::Data::AssetId assetId = asset.GetId(); Internal::MemoryAssetSystemNotificationBus::Broadcast(&Internal::MemoryAssetSystemNotifications::OnAssetReloaded, this); } } void ScriptCanvasMemoryAsset::OnAssetError(AZ::Data::Asset asset) { if (m_fileAssetId == asset.GetId()) { m_sourceInError = true; if (m_onAssetReadyCallback) { AZStd::invoke(m_onAssetReadyCallback, *this); } } else { AZ::Data::AssetId assetId = asset.GetId(); Internal::MemoryAssetSystemNotificationBus::Broadcast(&Internal::MemoryAssetSystemNotifications::OnAssetError, this); } } void ScriptCanvasMemoryAsset::OnAssetUnloaded(const AZ::Data::AssetId assetId, const AZ::Data::AssetType assetType) { if (m_fileAssetId == assetId) { AssetTrackerNotificationBus::Event(assetId, &AssetTrackerNotifications::OnAssetUnloaded, assetId, assetType); } } void ScriptCanvasMemoryAsset::SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid sourceAssetId) { // This updates the asset Id with the canonical assetId on SourceFileChanged // This occurs for new ScriptCanvas assets because before the SC asset is saved to disk, the asset database // has no asset Id associated with it, so this uses the supplied source path to find the asset Id registered AZStd::string fullPath; AzFramework::StringFunc::Path::Join(scanFolder.data(), relativePath.data(), fullPath); AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePath, fullPath); auto assetPathIdIt = AZStd::find(m_pendingSave.begin(), m_pendingSave.end(), fullPath); if (assetPathIdIt != m_pendingSave.end()) { AZ::SystemTickBus::Handler::BusDisconnect(); AZ::Data::AssetId previousFileAssetId; if (sourceAssetId != m_fileAssetId.m_guid) { previousFileAssetId = m_fileAssetId; // The source file has changed, store the AssetId to the canonical asset on file m_fileAssetId = sourceAssetId; } else if (!m_fileAssetId.IsValid()) { m_fileAssetId = sourceAssetId; } m_formerGraphIdPair = AZStd::make_pair(m_scriptCanvasId, m_graphId); m_fileState = Tracker::ScriptCanvasFileState::UNMODIFIED; // Connect to the source asset's bus to monitor for situations we may need to handle AZ::Data::AssetBus::MultiHandler::BusConnect(m_inMemoryAsset.GetId()); AZ::Data::AssetBus::MultiHandler::BusConnect(m_fileAssetId); m_pendingSave.erase(assetPathIdIt); if (m_onSaveCallback) { m_onSaveCallback(true, m_inMemoryAsset, previousFileAssetId); } } } void ScriptCanvasMemoryAsset::SourceFileRemoved(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid fileAssetId) { AZ_UNUSED(relativePath); AZ_UNUSED(scanFolder); if (m_fileAssetId == fileAssetId) { m_sourceRemoved = true; SignalFileStateChanged(); } } void ScriptCanvasMemoryAsset::SourceFileFailed(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid fileAssetId) { AZStd::string fullPath; AzFramework::StringFunc::Path::Join(scanFolder.data(), relativePath.data(), fullPath); AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePath, fullPath); auto assetPathIdIt = AZStd::find(m_pendingSave.begin(), m_pendingSave.end(), fullPath); if (assetPathIdIt != m_pendingSave.end()) { m_pendingSave.erase(assetPathIdIt); if (m_onSaveCallback) { m_onSaveCallback(false, m_inMemoryAsset, AZ::Data::AssetId()); } } } void ScriptCanvasMemoryAsset::FinalizeAssetSave(bool, const AzToolsFramework::SourceControlFileInfo& fileInfo, const AZ::Data::AssetStreamInfo& saveInfo, Callbacks::OnSave onSaveCallback) { auto fileIO = AZ::IO::FileIOBase::GetInstance(); if (fileInfo.IsLockedByOther()) { AZ_Error("Script Canvas", !fileInfo.IsLockedByOther(), "The file is already exclusively opened by another user: %s", fileInfo.m_filePath.data()); AZStd::invoke(onSaveCallback, false, m_inMemoryAsset, m_fileAssetId); return; } else if (fileInfo.IsReadOnly() && fileIO->Exists(fileInfo.m_filePath.c_str())) { AZ_Error("Script Canvas", !fileInfo.IsReadOnly(), "File %s is read-only. It cannot be saved." " If this file is in perforce it may not have been checked out by the Source Control API.", fileInfo.m_filePath.data()); AZStd::invoke(onSaveCallback, false, m_inMemoryAsset, m_fileAssetId); return; } AZStd::string normPath = saveInfo.m_streamName; AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePath, normPath); m_pendingSave.emplace_back(normPath); // Store the onSave callback so that we can call it from the proper place m_onSaveCallback = onSaveCallback; m_relativePath.clear(); m_scanFolder.clear(); m_sourceUuid = AZ::Data::AssetId(); ScriptCanvasEditor::SystemRequestBus::Broadcast(&ScriptCanvasEditor::SystemRequests::AddAsyncJob, [this, saveInfo, onSaveCallback]() { // ScriptCanvas Asset must be saved to a temporary location as the FileWatcher will pick up the file immediately if it detects any changes // and attempt to reload it AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::ScriptCanvas); const AZStd::string tempPath = MakeTemporaryFilePathForSave(saveInfo.m_streamName); AZ::IO::FileIOStream stream(tempPath.data(), saveInfo.m_streamFlags); if (stream.IsOpen()) { ScriptCanvasAssetHandler* assetHandler = nullptr; AssetTrackerRequestBus::BroadcastResult(assetHandler, &AssetTrackerRequests::GetAssetHandlerForType, m_inMemoryAsset.GetType()); AZ_Assert(assetHandler, "An asset handler must be found for an asset of type %s", AssetHelpers::AssetIdToString(m_inMemoryAsset.GetId())); bool savedSuccess; { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::ScriptCanvas, "ScriptCanvasAssetHandler::SaveAssetData"); savedSuccess = assetHandler->SaveAssetData(m_inMemoryAsset.Get(), &stream); } stream.Close(); if (savedSuccess) { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::ScriptCanvas, "AssetTracker::SaveAssetPostSourceControl : TempToTargetFileReplacement"); AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); const bool targetFileExists = fileIO->Exists(saveInfo.m_streamName.data()); bool removedTargetFile; { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::ScriptCanvas, "AssetTracker::SaveAssetPostSourceControl : TempToTargetFileReplacement : RemoveTarget"); removedTargetFile = fileIO->Remove(saveInfo.m_streamName.data()); } if (targetFileExists && !removedTargetFile) { savedSuccess = false; } else { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::ScriptCanvas, "AssetTracker::SaveAssetPostSourceControl : TempToTargetFileReplacement : RenameTempFile"); AZ::IO::Result renameResult = fileIO->Rename(tempPath.data(), saveInfo.m_streamName.data()); if (!renameResult) { savedSuccess = false; } } } if (savedSuccess) { AZStd::string watchFolder; AZ::Data::AssetInfo assetInfo; bool sourceInfoFound{}; AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, saveInfo.m_streamName.data(), assetInfo, watchFolder); if (sourceInfoFound) { // If we previously had a source file, it's possible to finish the save but not get a source file changed callback // because the asset might not produce a 'changed' output asset. // // Avoid this case in our callback flow by manually triggering that on the SystemTickBus // Storing the information we need to mimic the callback so everything acts the same, // regardless of whatever path this takes. if (!m_absolutePath.empty()) { m_triggerSourceChangedFromTickBus = true; m_relativePath = assetInfo.m_relativePath; m_scanFolder = watchFolder; m_sourceUuid = assetInfo.m_assetId; } AZ_TracePrintf("Script Canvas", "Script Canvas successfully saved as Asset \"%s\"", saveInfo.m_streamName.data()); m_absolutePath = m_saveAsPath; } else { AZ_TracePrintf("Script Canvas", "Script Canvas successfully saved as Asset \"%s\" but is outside of project scope and cannot be loaded.", saveInfo.m_streamName.data()); m_triggerErrorSaveCallback = true; } m_sourceRemoved = false; m_saveAsPath.clear(); } } else { AZ_TracePrintf("Script Canvas", "Script Canvas failed to open temporary file, unable to complete save action."); m_triggerErrorSaveCallback = true; } }); // Because this is connecting from within the lambda, the this is necessary. Otherwise it 'connects' but won't actually trigger. AZ::SystemTickBus::Handler::BusConnect(); } AZ::Data::Asset ScriptCanvasMemoryAsset::CloneAssetData(AZ::Data::AssetId newAssetId) { if (m_assetType == azrtti_typeid()) { return CloneAssetData(newAssetId); } else if (m_assetType == azrtti_typeid()) { return CloneAssetData(newAssetId); } AZ_Assert(false, "The provides asset type is not supported as a valid Script Canvas memory asset"); return AZ::Data::Asset(); } void ScriptCanvasMemoryAsset::OnUndoStackChanged() { UndoNotificationBus::Broadcast(&UndoNotifications::OnCanUndoChanged, m_undoState->m_undoStack->CanUndo()); UndoNotificationBus::Broadcast(&UndoNotifications::OnCanRedoChanged, m_undoState->m_undoStack->CanRedo()); } void ScriptCanvasMemoryAsset::SignalFileStateChanged() { MemoryAssetNotificationBus::Event(m_fileAssetId, &MemoryAssetNotifications::OnFileStateChanged, GetFileState()); } AZStd::string ScriptCanvasMemoryAsset::MakeTemporaryFilePathForSave(AZStd::string_view targetFilename) { AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); AZ_Assert(fileIO, "File IO is not initialized."); AZStd::string tempFilename; AzFramework::StringFunc::Path::GetFullFileName(targetFilename.data(), tempFilename); AZStd::string tempPath = AZStd::string::format("@cache@/scriptcanvas/%s.temp", tempFilename.data()); AZStd::array resolvedPath{}; AZ::IO::FileIOBase::GetInstance()->ResolvePath(tempPath.data(), resolvedPath.data(), resolvedPath.size()); return resolvedPath.data(); } ScriptCanvasAssetHandler* ScriptCanvasMemoryAsset::GetAssetHandlerForType(AZ::Data::AssetType assetType) { ScriptCanvasAssetHandler* assetHandler = nullptr; AZ::EBusAggregateResults foundAssetHandlers; ScriptCanvas::AssetRegistryRequestBus::BroadcastResult(foundAssetHandlers, &ScriptCanvas::AssetRegistryRequests::GetAssetHandler); for (auto handler : foundAssetHandlers.values) { if (handler != nullptr) { ScriptCanvasAssetHandler* theHandler = azrtti_cast(handler); if (theHandler != nullptr && theHandler->GetAssetType() == assetType) { assetHandler = theHandler; break; } } } AZ_Assert(assetHandler, "The specified asset type does not have a registered asset handler."); return assetHandler; } AZ::EntityId ScriptCanvasMemoryAsset::GetEditorEntityIdFromSceneEntityId(AZ::EntityId sceneEntityId) { if (m_editorEntityIdMap.find(sceneEntityId) != m_editorEntityIdMap.end()) { return m_editorEntityIdMap[sceneEntityId]; } return AZ::EntityId(); } AZ::EntityId ScriptCanvasMemoryAsset::GetSceneEntityIdFromEditorEntityId(AZ::EntityId editorEntityId) { for (auto mapEntry : m_editorEntityIdMap) { if (mapEntry.second == editorEntityId) { return mapEntry.first; } } return AZ::EntityId(); } /////////////// // UndoHelper /////////////// UndoHelper::UndoHelper(ScriptCanvasMemoryAsset& memoryAsset) : m_memoryAsset(memoryAsset) { UndoRequestBus::Handler::BusConnect(memoryAsset.GetScriptCanvasId()); } UndoHelper::~UndoHelper() { UndoRequestBus::Handler::BusDisconnect(); } ScriptCanvasEditor::UndoCache* UndoHelper::GetSceneUndoCache() { return m_memoryAsset.GetUndoState()->m_undoCache.get(); } ScriptCanvasEditor::UndoData UndoHelper::CreateUndoData() { AZ::EntityId graphCanvasGraphId = m_memoryAsset.GetGraphId(); ScriptCanvas::ScriptCanvasId scriptCanvasId = m_memoryAsset.GetScriptCanvasId(); GraphCanvas::GraphModelRequestBus::Event(graphCanvasGraphId, &GraphCanvas::GraphModelRequests::OnSaveDataDirtied, graphCanvasGraphId); UndoData undoData; ScriptCanvas::GraphData* graphData{}; ScriptCanvas::GraphRequestBus::EventResult(graphData, scriptCanvasId, &ScriptCanvas::GraphRequests::GetGraphData); const ScriptCanvas::VariableData* varData{}; ScriptCanvas::GraphVariableManagerRequestBus::EventResult(varData, scriptCanvasId, &ScriptCanvas::GraphVariableManagerRequests::GetVariableDataConst); if (graphData && varData) { undoData.m_graphData = *graphData; undoData.m_variableData = *varData; EditorGraphRequestBus::EventResult(undoData.m_visualSaveData, scriptCanvasId, &EditorGraphRequests::GetGraphCanvasSaveData); } return undoData; } void UndoHelper::BeginUndoBatch(AZStd::string_view label) { m_memoryAsset.GetUndoState()->BeginUndoBatch(label); } void UndoHelper::EndUndoBatch() { m_memoryAsset.GetUndoState()->EndUndoBatch(); } void UndoHelper::AddUndo(AzToolsFramework::UndoSystem::URSequencePoint* sequencePoint) { if (SceneUndoState * sceneUndoState = m_memoryAsset.GetUndoState()) { if (!sceneUndoState->m_currentUndoBatch) { sceneUndoState->m_undoStack->Post(sequencePoint); } else { sequencePoint->SetParent(sceneUndoState->m_currentUndoBatch); } } } void UndoHelper::AddGraphItemChangeUndo(AZStd::string_view undoLabel) { GraphItemChangeCommand* command = aznew GraphItemChangeCommand(undoLabel); command->Capture(m_memoryAsset, true); command->Capture(m_memoryAsset, false); AddUndo(command); } void UndoHelper::AddGraphItemAdditionUndo(AZStd::string_view undoLabel) { GraphItemAddCommand* command = aznew GraphItemAddCommand(undoLabel); command->Capture(m_memoryAsset, false); AddUndo(command); } void UndoHelper::AddGraphItemRemovalUndo(AZStd::string_view undoLabel) { GraphItemRemovalCommand* command = aznew GraphItemRemovalCommand(undoLabel); command->Capture(m_memoryAsset, true); AddUndo(command); } void UndoHelper::Undo() { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::ScriptCanvas); SceneUndoState* sceneUndoState = m_memoryAsset.GetUndoState(); if (sceneUndoState) { AZ_Warning("Script Canvas", !sceneUndoState->m_currentUndoBatch, "Script Canvas Editor has an open undo batch when performing a redo operation"); if (sceneUndoState->m_undoStack->CanUndo()) { m_status = Status::InUndo; sceneUndoState->m_undoStack->Undo(); m_status = Status::Idle; UpdateCache(); } } } void UndoHelper::Redo() { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::ScriptCanvas); SceneUndoState* sceneUndoState = m_memoryAsset.GetUndoState(); if (sceneUndoState) { AZ_Warning("Script Canvas", !sceneUndoState->m_currentUndoBatch, "Script Canvas Editor has an open undo batch when performing a redo operation"); if (sceneUndoState->m_undoStack->CanRedo()) { m_status = Status::InUndo; sceneUndoState->m_undoStack->Redo(); m_status = Status::Idle; UpdateCache(); } } } void UndoHelper::Reset() { if (SceneUndoState* sceneUndoState = m_memoryAsset.GetUndoState()) { AZ_Warning("Script Canvas", !sceneUndoState->m_currentUndoBatch, "Script Canvas Editor has an open undo batch when reseting the undo stack"); sceneUndoState->m_undoStack->Reset(); } } bool UndoHelper::IsIdle() { return m_status == Status::Idle; } bool UndoHelper::IsActive() { return m_status != Status::Idle; } bool UndoHelper::CanUndo() const { return m_memoryAsset.GetUndoState()->m_undoStack->CanUndo(); } bool UndoHelper::CanRedo() const { return m_memoryAsset.GetUndoState()->m_undoStack->CanRedo(); } void UndoHelper::UpdateCache() { ScriptCanvas::ScriptCanvasId scriptCanvasId = m_memoryAsset.GetScriptCanvasId(); UndoCache* undoCache = nullptr; UndoRequestBus::EventResult(undoCache, scriptCanvasId, &UndoRequests::GetSceneUndoCache); if (undoCache) { undoCache->UpdateCache(scriptCanvasId); } } }