/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ScriptCanvasEditor { static bool EditorScriptCanvasComponentVersionConverter(AZ::SerializeContext& serializeContext, AZ::SerializeContext::DataElementNode& rootElement) { if (rootElement.GetVersion() <= 4) { int assetElementIndex = rootElement.FindElement(AZ::Crc32("m_asset")); if (assetElementIndex == -1) { return false; } auto assetElement = rootElement.GetSubElement(assetElementIndex); AZ::Data::Asset scriptCanvasAsset; if (!assetElement.GetData(scriptCanvasAsset)) { AZ_Error("Script Canvas", false, "Unable to find Script Canvas Asset on a Version %u Editor ScriptCanvas Component", rootElement.GetVersion()); return false; } ScriptCanvasAssetHolder assetHolder; assetHolder.SetAsset(scriptCanvasAsset.GetId()); if (!rootElement.AddElementWithData(serializeContext, "m_assetHolder", assetHolder)) { AZ_Error("Script Canvas", false, "Unable to add ScriptCanvas Asset Holder element when converting from version %u", rootElement.GetVersion()) } rootElement.RemoveElementByName(AZ_CRC("m_asset", 0x4e58e538)); rootElement.RemoveElementByName(AZ_CRC("m_openEditorButton", 0x9bcb3d5b)); } if (rootElement.GetVersion() <= 6) { rootElement.RemoveElementByName(AZ_CRC("m_originalData", 0x2aee5989)); } return true; } //========================================================================= void EditorScriptCanvasComponent::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(7, &EditorScriptCanvasComponentVersionConverter) ->Field("m_name", &EditorScriptCanvasComponent::m_name) ->Field("m_assetHolder", &EditorScriptCanvasComponent::m_scriptCanvasAssetHolder) ->Field("m_variableData", &EditorScriptCanvasComponent::m_editableData) ->Field("m_variableEntityIdMap", &EditorScriptCanvasComponent::m_variableEntityIdMap) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class("Script Canvas", "The Script Canvas component allows you to add a Script Canvas asset to a component, and have it execute on the specified entity.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Scripting") ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/ScriptCanvas/ScriptCanvas.svg") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/ScriptCanvas/Viewport/ScriptCanvas.png") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::PrimaryAssetType, ScriptCanvasAssetHandler::GetAssetTypeStatic()) ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0)) ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-script-canvas.html") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorScriptCanvasComponent::m_scriptCanvasAssetHolder, "Script Canvas Asset", "Script Canvas asset associated with this component") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorScriptCanvasComponent::m_editableData, "Properties", "Script Canvas Graph Properties") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ; } } } EditorScriptCanvasComponent::EditorScriptCanvasComponent() : EditorScriptCanvasComponent(AZ::Data::Asset()) { } EditorScriptCanvasComponent::EditorScriptCanvasComponent(AZ::Data::Asset asset) : m_scriptCanvasAssetHolder() { if (asset.GetId().IsValid()) { m_scriptCanvasAssetHolder.SetAsset(asset.GetId()); } m_scriptCanvasAssetHolder.SetScriptChangedCB([this](AZ::Data::AssetId assetId) { OnScriptCanvasAssetChanged(assetId); }); } EditorScriptCanvasComponent::~EditorScriptCanvasComponent() { AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); } void EditorScriptCanvasComponent::UpdateName() { AZ::Data::AssetId assetId = m_scriptCanvasAssetHolder.GetAssetId(); if (assetId.IsValid()) { // Pathname from the asset doesn't seem to return a value unless the asset has been loaded up once(which isn't done until we try to show it). // Using the Job system to determine the asset name instead. AZ::Outcome jobOutcome = AZ::Failure(); AzToolsFramework::AssetSystemJobRequestBus::BroadcastResult(jobOutcome, &AzToolsFramework::AssetSystemJobRequestBus::Events::GetAssetJobsInfoByAssetID, assetId, false, false); AZStd::string assetPath; AZStd::string assetName; if (jobOutcome.IsSuccess()) { AzToolsFramework::AssetSystem::JobInfoContainer& jobs = jobOutcome.GetValue(); // Get the asset relative path if (!jobs.empty()) { assetPath = jobs[0].m_sourceFile; } // Get the asset file name assetName = assetPath; if (!assetPath.empty()) { AzFramework::StringFunc::Path::GetFileName(assetPath.c_str(), assetName); SetName(assetName); } } } } void EditorScriptCanvasComponent::OpenEditor() { m_scriptCanvasAssetHolder.OpenEditor(); } void EditorScriptCanvasComponent::CloseGraph() { AZ::Data::AssetId assetId = m_scriptCanvasAssetHolder.GetAssetId(); if (assetId.IsValid()) { GeneralRequestBus::Broadcast(&GeneralRequests::CloseScriptCanvasAsset, assetId); } } void EditorScriptCanvasComponent::Init() { EditorComponentBase::Init(); AzFramework::AssetCatalogEventBus::Handler::BusConnect(); m_scriptCanvasAssetHolder.Init(GetEntityId(), GetId()); } //========================================================================= void EditorScriptCanvasComponent::Activate() { EditorComponentBase::Activate(); AZ::EntityId entityId = GetEntityId(); EditorContextMenuRequestBus::Handler::BusConnect(entityId); EditorScriptCanvasComponentRequestBus::Handler::BusConnect(entityId); EditorScriptCanvasComponentLoggingBus::Handler::BusConnect(entityId); EditorLoggingComponentNotificationBus::Broadcast(&EditorLoggingComponentNotifications::OnEditorScriptCanvasComponentActivated, GetNamedEntityId(), GetGraphIdentifier()); AZ::Data::AssetId fileAssetId = m_scriptCanvasAssetHolder.GetAssetId(); if (fileAssetId.IsValid()) { AssetTrackerNotificationBus::Handler::BusConnect(fileAssetId); ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, fileAssetId); if (memoryAsset && memoryAsset->GetAsset().GetStatus() == AZ::Data::AssetData::AssetStatus::Ready) { OnScriptCanvasAssetReady(memoryAsset); } else { AssetTrackerRequestBus::Broadcast(&AssetTrackerRequests::Load, m_scriptCanvasAssetHolder.GetAssetId(), m_scriptCanvasAssetHolder.GetAssetType(), nullptr); } } } //========================================================================= void EditorScriptCanvasComponent::Deactivate() { AssetTrackerNotificationBus::Handler::BusDisconnect(); EditorScriptCanvasComponentLoggingBus::Handler::BusDisconnect(); EditorLoggingComponentNotificationBus::Broadcast(&EditorLoggingComponentNotifications::OnEditorScriptCanvasComponentDeactivated, GetNamedEntityId(), GetGraphIdentifier()); EditorComponentBase::Deactivate(); //EditorScriptCanvasAssetNotificationBus::Handler::BusDisconnect(); EditorScriptCanvasComponentRequestBus::Handler::BusDisconnect(); EditorContextMenuRequestBus::Handler::BusDisconnect(); } //========================================================================= void EditorScriptCanvasComponent::BuildGameEntity(AZ::Entity* gameEntity) { AZ::Data::AssetId assetId = m_scriptCanvasAssetHolder.GetAssetId(); AZ::Data::AssetId runtimeAssetId(assetId.m_guid, AZ_CRC("RuntimeData", 0x163310ae)); AZ::Data::Asset runtimeAsset(runtimeAssetId, azrtti_typeid()); auto executionComponent = gameEntity->CreateComponent(runtimeAsset); ScriptCanvas::VariableData varData; for (const auto& varConfig : m_editableData.GetVariables()) { if (varConfig.m_graphVariable.GetDatum()->Empty()) { AZ_Error("ScriptCanvas", false, "Data loss detected for GraphVariable ('%s') on Entity ('%s' - '%s') Graph ('%s')" , varConfig.m_graphVariable.GetVariableName().data() , gameEntity->GetName().c_str() , GetEntityId().ToString().c_str() , GetName().c_str()); } else { varData.AddVariable(varConfig.m_graphVariable.GetVariableName(), varConfig.m_graphVariable); } } executionComponent->SetVariableOverrides(varData); executionComponent->SetVariableEntityIdMap(m_variableEntityIdMap); } void EditorScriptCanvasComponent::OnCatalogAssetAdded(const AZ::Data::AssetId& assetId) { // If we removed out asset due to the catalog removing. Just set it back. if (m_removedCatalogId == assetId) { if (!m_scriptCanvasAssetHolder.GetAssetId().IsValid()) { SetPrimaryAsset(assetId); m_removedCatalogId.SetInvalid(); } } } void EditorScriptCanvasComponent::OnCatalogAssetRemoved(const AZ::Data::AssetId& removedAssetId, const AZ::Data::AssetInfo& /*assetInfo*/) { AZ::Data::AssetId assetId = m_scriptCanvasAssetHolder.GetAssetId(); // If the Asset gets removed from disk while the Editor is loaded clear out the asset reference. if (assetId == removedAssetId) { m_removedCatalogId = assetId; SetPrimaryAsset({}); } } void EditorScriptCanvasComponent::SetPrimaryAsset(const AZ::Data::AssetId& assetId) { m_scriptCanvasAssetHolder.ClearAsset(); if (assetId.IsValid()) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetId); if (memoryAsset) { m_scriptCanvasAssetHolder.SetAsset(memoryAsset->GetFileAssetId()); OnScriptCanvasAssetChanged(memoryAsset->GetFileAssetId()); SetName(memoryAsset->GetTabName()); } else { auto scriptCanvasAsset = AZ::Data::AssetManager::Instance().FindAsset(assetId); if (scriptCanvasAsset) { m_scriptCanvasAssetHolder.SetAsset(assetId); } } } AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_AttributesAndValues); } AZ::Data::AssetId EditorScriptCanvasComponent::GetAssetId() const { return m_scriptCanvasAssetHolder.GetAssetId(); } AZ::EntityId EditorScriptCanvasComponent::GetGraphEntityId() const { AZ::EntityId scriptCanvasEntityId; AZ::Data::AssetId assetId = m_scriptCanvasAssetHolder.GetAssetId(); if (assetId.IsValid()) { AssetTrackerRequestBus::BroadcastResult(scriptCanvasEntityId, &AssetTrackerRequests::GetScriptCanvasId, assetId); } return scriptCanvasEntityId; } void EditorScriptCanvasComponent::OnAssetReady(const ScriptCanvasMemoryAsset::pointer asset) { OnScriptCanvasAssetReady(asset); } void EditorScriptCanvasComponent::OnAssetSaved(const ScriptCanvasMemoryAsset::pointer asset, bool isSuccessful) { if (isSuccessful) { OnScriptCanvasAssetReady(asset); } } void EditorScriptCanvasComponent::OnAssetReloaded(const ScriptCanvasMemoryAsset::pointer asset) { OnScriptCanvasAssetReady(asset); } void EditorScriptCanvasComponent::OnScriptCanvasAssetChanged(AZ::Data::AssetId assetId) { AssetTrackerNotificationBus::Handler::BusDisconnect(); ScriptCanvas::GraphIdentifier newIdentifier = GetGraphIdentifier(); newIdentifier.m_assetId = assetId; ScriptCanvas::GraphIdentifier oldIdentifier = GetGraphIdentifier(); oldIdentifier.m_assetId = m_previousAssetId; EditorLoggingComponentNotificationBus::Broadcast(&EditorLoggingComponentNotifications::OnAssetSwitched, GetNamedEntityId(), newIdentifier, oldIdentifier); m_previousAssetId = m_scriptCanvasAssetHolder.GetAssetId(); // Only clear our variables when we are given a new asset id // or when the asset was explicitly set to empty. // // i.e. do not clear variables when we lose the catalog asset. if ((assetId.IsValid() && assetId != m_removedCatalogId) || (!assetId.IsValid() && !m_removedCatalogId.IsValid())) { ClearVariables(); } if (assetId.IsValid()) { AssetTrackerNotificationBus::Handler::BusConnect(assetId); ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetId); if (memoryAsset && memoryAsset->GetAsset().GetStatus() == AZ::Data::AssetData::AssetStatus::Ready) { OnScriptCanvasAssetReady(memoryAsset); } } } void EditorScriptCanvasComponent::SetAssetId(const AZ::Data::AssetId& assetId) { if (m_scriptCanvasAssetHolder.GetAssetId() != assetId) { // Invalidate the previously removed catalog id if we are setting a new asset id m_removedCatalogId.SetInvalid(); SetPrimaryAsset(assetId); } } bool EditorScriptCanvasComponent::HasAssetId() const { return m_scriptCanvasAssetHolder.GetAssetId().IsValid(); } ScriptCanvas::GraphIdentifier EditorScriptCanvasComponent::GetGraphIdentifier() const { // For now we don't want to deal with disambiguating duplicates of the same script running on one entity. // Should that change we need to add the component id back into this. return ScriptCanvas::GraphIdentifier(m_scriptCanvasAssetHolder.GetAssetId(), 0); } void EditorScriptCanvasComponent::OnScriptCanvasAssetReady(const ScriptCanvasMemoryAsset::pointer memoryAsset) { if (memoryAsset->GetFileAssetId() == m_scriptCanvasAssetHolder.GetAssetId()) { LoadVariables(memoryAsset); UpdateName(); } } /*! Start Variable Block Implementation */ void EditorScriptCanvasComponent::AddVariable(AZStd::string_view varName, const ScriptCanvas::GraphVariable& graphVariable) { bool exposeVariableToComponent = graphVariable.IsInScope(ScriptCanvas::VariableFlags::Scope::Input); if (!exposeVariableToComponent) { return; } const auto& variableId = graphVariable.GetVariableId(); ScriptCanvas::EditableVariableConfiguration* originalVarNameValuePair = m_editableData.FindVariable(variableId); if (!originalVarNameValuePair) { m_editableData.AddVariable(varName, graphVariable); originalVarNameValuePair = m_editableData.FindVariable(variableId); } if (!originalVarNameValuePair) { AZ_Error("Script Canvas", false, "Unable to find variable with id %s and name %s on the ScriptCanvas Component. There is an issue in AddVariable", variableId.ToString().data(), varName.data()); return; } // Update the variable name as it may have changed originalVarNameValuePair->m_graphVariable.SetVariableName(varName); originalVarNameValuePair->m_graphVariable.SetExposureCategory(graphVariable.GetExposureCategory()); originalVarNameValuePair->m_graphVariable.SetScriptInputControlVisibility(AZ::Edit::PropertyVisibility::Hide); originalVarNameValuePair->m_graphVariable.SetAllowSignalOnChange(false); } void EditorScriptCanvasComponent::AddNewVariables(const ScriptCanvas::VariableData& graphVarData) { for (auto&& variablePair : graphVarData.GetVariables()) { AddVariable(variablePair.second.GetVariableName(), variablePair.second); } } void EditorScriptCanvasComponent::RemoveVariable(const ScriptCanvas::VariableId& varId) { m_editableData.RemoveVariable(varId); } void EditorScriptCanvasComponent::RemoveOldVariables(const ScriptCanvas::VariableData& graphVarData) { AZStd::vector oldVariableIds; for (auto varConfig : m_editableData.GetVariables()) { const auto& variableId = varConfig.m_graphVariable.GetVariableId(); auto graphVariable = graphVarData.FindVariable(variableId); if (!graphVariable || !graphVariable->IsInScope(ScriptCanvas::VariableFlags::Scope::Input)) { oldVariableIds.push_back(variableId); } } for (const auto& oldVariableId : oldVariableIds) { RemoveVariable(oldVariableId); } } bool EditorScriptCanvasComponent::UpdateVariable(const ScriptCanvas::GraphVariable& graphDatum, ScriptCanvas::GraphVariable& updateDatum, ScriptCanvas::GraphVariable& originalDatum) { // If the editable datum is the different than the original datum, then the "variable value" has been overridden on this component // Variable values only propagate from the Script Canvas graph to this component if the original "variable value" has not been overridden // by the editable "variable value" on this component and the "variable value" on the graph is different than the variable value on this component auto isNotOverridden = (*updateDatum.GetDatum()) == (*originalDatum.GetDatum()); auto scGraphIsModified = (*originalDatum.GetDatum()) != (*graphDatum.GetDatum()); if (isNotOverridden && scGraphIsModified) { ScriptCanvas::ModifiableDatumView originalDatumView; originalDatum.ConfigureDatumView(originalDatumView); originalDatumView.AssignToDatum((*graphDatum.GetDatum())); ScriptCanvas::ModifiableDatumView updatedDatumView; updateDatum.ConfigureDatumView(updatedDatumView); updatedDatumView.AssignToDatum((*graphDatum.GetDatum())); return true; } return false; } void EditorScriptCanvasComponent::LoadVariables(const ScriptCanvasMemoryAsset::pointer memoryAsset) { auto assetData = memoryAsset->GetAsset(); AZ::Entity* scriptCanvasEntity = assetData->GetScriptCanvasEntity(); AZ_Assert(scriptCanvasEntity, "This graph must have a valid entity"); auto variableComponent = scriptCanvasEntity ? AZ::EntityUtils::FindFirstDerivedComponent(scriptCanvasEntity) : nullptr; if (variableComponent) { m_variableEntityIdMap.clear(); for (const auto& varIdToVariablePair : variableComponent->GetVariableData()->GetVariables()) { if (const AZ::EntityId* entityIdVariable = varIdToVariablePair.second.GetDatum()->GetAs()) { if (entityIdVariable->IsValid() && (*entityIdVariable) != ScriptCanvas::GraphOwnerId) { m_variableEntityIdMap.emplace(static_cast(*entityIdVariable), *entityIdVariable); } } } // Add properties from the SC Asset to the SC Component if they do not exist on the SC Component AddNewVariables(*variableComponent->GetVariableData()); RemoveOldVariables(*variableComponent->GetVariableData()); } AzToolsFramework::ToolsApplicationNotificationBus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree_NewContent); } void EditorScriptCanvasComponent::ClearVariables() { m_editableData.Clear(); } /* End Variable Block Implementation*/ }