/* * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include "precompiled.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ScriptCanvasBuilder { AssetHandlers::AssetHandlers(SharedHandlers& source) : m_editorAssetHandler(source.m_editorAssetHandler.first) , m_editorFunctionAssetHandler(source.m_editorFunctionAssetHandler.first) , m_runtimeAssetHandler(source.m_runtimeAssetHandler.first) , m_subgraphInterfaceHandler(source.m_subgraphInterfaceHandler.first) {} void SharedHandlers::DeleteOwnedHandlers() { DeleteIfOwned(m_editorAssetHandler); DeleteIfOwned(m_editorFunctionAssetHandler); DeleteIfOwned(m_runtimeAssetHandler); DeleteIfOwned(m_subgraphInterfaceHandler); } void SharedHandlers::DeleteIfOwned(HandlerOwnership& handler) { if (handler.second) { AZ::Data::AssetManager::Instance().UnregisterHandler(handler.first); delete handler.first; handler.first = nullptr; } } AZ::Outcome ParseGraph(AZ::Entity& buildEntity, AZStd::string_view graphPath) { AZStd::string fileNameOnly; AzFramework::StringFunc::Path::GetFullFileName(graphPath.data(), fileNameOnly); ScriptCanvas::Grammar::Request request; request.graph = PrepareSourceGraph(&buildEntity); if (!request.graph) { return AZ::Failure(AZStd::string("build entity did not have source graph components")); } request.rawSaveDebugOutput = ScriptCanvas::Grammar::g_saveRawTranslationOuputToFileAtPrefabTime; request.printModelToConsole = ScriptCanvas::Grammar::g_printAbstractCodeModelAtPrefabTime; request.name = fileNameOnly.empty() ? fileNameOnly : "BuilderGraph"; request.addDebugInformation = false; return ScriptCanvas::Translation::ParseGraph(request); } AZ::Outcome CreateLuaAsset(AZ::Entity* buildEntity, AZ::Data::AssetId scriptAssetId, AZStd::string_view rawLuaFilePath) { AZStd::string fullPath(rawLuaFilePath); AZStd::string fileNameOnly; AzFramework::StringFunc::Path::GetFullFileName(rawLuaFilePath.data(), fileNameOnly); AzFramework::StringFunc::Path::Normalize(fullPath); auto sourceGraph = PrepareSourceGraph(buildEntity); ScriptCanvas::Grammar::Request request; request.scriptAssetId = scriptAssetId; request.graph = sourceGraph; request.name = fileNameOnly; request.rawSaveDebugOutput = ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile; request.printModelToConsole = ScriptCanvas::Grammar::g_printAbstractCodeModel; request.path = fullPath; bool pathFound = false; AZStd::string relativePath; AzToolsFramework::AssetSystemRequestBus::BroadcastResult (pathFound , &AzToolsFramework::AssetSystem::AssetSystemRequest::GetRelativeProductPathFromFullSourceOrProductPath , fullPath.c_str(), relativePath); if (!pathFound) { AZ::Failure(AZStd::string::format("Failed to get engine relative path from %s", fullPath.c_str())); } request.namespacePath = relativePath; const ScriptCanvas::Translation::Result translationResult = TranslateToLua(request); auto isSuccessOutcome = translationResult.IsSuccess(ScriptCanvas::Translation::TargetFlags::Lua); if (!isSuccessOutcome.IsSuccess()) { return AZ::Failure(isSuccessOutcome.TakeError()); } auto& translation = translationResult.m_translations.find(ScriptCanvas::Translation::TargetFlags::Lua)->second; AZ::Data::Asset asset; scriptAssetId.m_subId = AZ::ScriptAsset::CompiledAssetSubId; asset.Create(scriptAssetId); auto writeStream = asset.Get()->CreateWriteStream(); AZ::IO::MemoryStream inputStream(translation.m_text.data(), translation.m_text.size()); AzFramework::ScriptCompileRequest compileRequest; compileRequest.m_errorWindow = s_scriptCanvasBuilder; compileRequest.m_input = &inputStream; compileRequest.m_output = &writeStream; AzFramework::ConstructScriptAssetPaths(compileRequest); auto compileOutcome = AzFramework::CompileScript(compileRequest); if (!compileOutcome.IsSuccess()) { return AZ::Failure(AZStd::string(compileOutcome.TakeError())); } ScriptCanvas::Translation::LuaAssetResult result; result.m_scriptAsset = asset; result.m_runtimeInputs = AZStd::move(translation.m_runtimeInputs); result.m_debugMap = AZStd::move(translation.m_debugMap); result.m_dependencies = translationResult.m_model->GetOrderedDependencies(); result.m_parseDuration = translationResult.m_parseDuration; result.m_translationDuration = translation.m_duration; return AZ::Success(result); } AZ::Outcome, AZStd::string> CreateRuntimeAsset(const AZ::Data::Asset& editAsset) { // Flush asset manager events to ensure no asset references are held by closures queued on Ebuses. AZ::Data::AssetManager::Instance().DispatchEvents(); auto runtimeAssetId = editAsset.GetId(); runtimeAssetId.m_subId = AZ_CRC("RuntimeData", 0x163310ae); AZ::Data::Asset runtimeAsset; runtimeAsset.Create(runtimeAssetId); return AZ::Success(runtimeAsset); } AZ::Outcome, AZStd::string> CreateRuntimeFunctionAsset(const AZ::Data::Asset& editAsset) { // Flush asset manager events to ensure no asset references are held by closures queued on Ebuses. AZ::Data::AssetManager::Instance().DispatchEvents(); auto runtimeAssetId = editAsset.GetId(); runtimeAssetId.m_subId = AZ_CRC("SubgraphInterface", 0xdfe6dc72); AZ::Data::Asset runtimeAsset; runtimeAsset.Create(runtimeAssetId); return AZ::Success(runtimeAsset); } AZ::Outcome CompileGraphData(AZ::Entity* scriptCanvasEntity) { typedef AZStd::pair< ScriptCanvas::Node*, AZ::Entity* > NodeEntityPair; if (!scriptCanvasEntity) { return AZ::Failure(AZStd::string("Cannot compile graph data from a nullptr Script Canvas Entity")); } auto sourceGraph = AZ::EntityUtils::FindFirstDerivedComponent(scriptCanvasEntity); if (!sourceGraph) { return AZ::Failure(AZStd::string("Failed to find Script Canvas Graph Component")); } const ScriptCanvas::GraphData& sourceGraphData = *sourceGraph->GetGraphDataConst(); ScriptCanvas::GraphData compiledGraphData; auto serializeContext = AZ::EntityUtils::GetApplicationSerializeContext(); serializeContext->CloneObjectInplace(compiledGraphData, &sourceGraphData); AZStd::unordered_set disabledEndpoints; AZStd::unordered_set disabledNodeEntities; AZStd::unordered_map nodeLookUpMap; AZStd::unordered_set deletedNodeEntities; { auto nodeIter = compiledGraphData.m_nodes.begin(); while (nodeIter != compiledGraphData.m_nodes.end()) { AZ::Entity* nodeEntity = (*nodeIter); auto nodeComponent = AZ::EntityUtils::FindFirstDerivedComponent(nodeEntity); if (nodeComponent == nullptr) { deletedNodeEntities.insert(nodeEntity->GetId()); delete nodeEntity; nodeIter = compiledGraphData.m_nodes.erase(nodeIter); continue; } bool disabledNode = false; if (!nodeComponent->IsNodeEnabled()) { disabledNode = true; auto nodeSlots = nodeComponent->GetAllSlots(); for (const ScriptCanvas::Slot* slot : nodeSlots) { // While slot has a GetEndpoint method. It uses the Node pointer which has not been set yet. // Bypass it for the more manual way in the builder. disabledEndpoints.insert(ScriptCanvas::Endpoint(nodeEntity->GetId(), slot->GetId())); } } if (disabledNode) { delete nodeEntity; nodeIter = compiledGraphData.m_nodes.erase(nodeIter); } else { // Keep them in the map to make future look-ups easier. nodeLookUpMap[nodeEntity->GetId()] = AZStd::make_pair(nodeComponent, nodeEntity); ++nodeIter; } } } // Keep track of all the endpoints we've fully removed so we can cleanse out all of the invalid connections AZStd::unordered_set fullyRemovedEndpoints; while (!disabledEndpoints.empty()) { // Keep track of the list of all the potentially disabled nodes that are a result of any of the current batch of disabled endpoints AZStd::unordered_set< NodeEntityPair > potentiallyDisabledNodes; for (const ScriptCanvas::Endpoint& disabledEndpoint : disabledEndpoints) { fullyRemovedEndpoints.insert(disabledEndpoint); AZStd::unordered_set reversedEndpoints; auto mapRange = compiledGraphData.m_endpointMap.equal_range(disabledEndpoint); // Start by looking up all of our connected endpoints so we can clean-up this map for (auto endpointIter = mapRange.first; endpointIter != mapRange.second; ++endpointIter) { reversedEndpoints.insert(endpointIter->second); } // Remove all of the endpoint entries relating to the currently disabled map. compiledGraphData.m_endpointMap.erase(disabledEndpoint); // Go through all of the reversed connections and find all of the corresponding entries that // match our removed connection and remove them as well. for (auto disconnectedEndpoint : reversedEndpoints) { auto mapRange2 = compiledGraphData.m_endpointMap.equal_range(disconnectedEndpoint); for (auto endpointIter = mapRange2.first; endpointIter != mapRange2.second; ++endpointIter) { if (endpointIter->second == disabledEndpoint) { compiledGraphData.m_endpointMap.erase(endpointIter); break; } } // Look up the node so we can do some introspection on the slot. auto nodeIter = nodeLookUpMap.find(disconnectedEndpoint.GetNodeId()); if (nodeIter != nodeLookUpMap.end()) { ScriptCanvas::Node* node = nodeIter->second.first; ScriptCanvas::Slot* slot = node->GetSlot(disconnectedEndpoint.GetSlotId()); // If the slot is an input, we want to recurse on that as a potentially disabled node. So keep track of it and we'll parse it in the next step. if (slot && slot->IsExecution() && slot->IsInput()) { // If we no longer have any active connections. We can also strip out this node to simplify down the graph further. if (compiledGraphData.m_endpointMap.count(disconnectedEndpoint) == 0) { potentiallyDisabledNodes.insert(nodeIter->second); } } } } } disabledEndpoints.clear(); for (const NodeEntityPair& nodePair : potentiallyDisabledNodes) { ScriptCanvas::Node* node = nodePair.first; auto inputSlots = node->GetAllSlotsByDescriptor(ScriptCanvas::SlotDescriptors::ExecutionIn()); bool hasExecutionIn = false; for (const ScriptCanvas::Slot* slot : inputSlots) { // Can't use the slot method since it requires the node to be registered which has not happened yet. ScriptCanvas::Endpoint endpoint = { nodePair.second->GetId(), slot->GetId() }; if (compiledGraphData.m_endpointMap.count(endpoint) > 0) { hasExecutionIn = true; break; } } // Once here we know the node has no input but was apart of the previous chain so we can just disable the entire node. // Since it will no longer have any incoming data via the disabled checks from above. if (!hasExecutionIn) { auto disabledSlots = node->GetAllSlots(); for (auto disabledSlot : disabledSlots) { ScriptCanvas::Endpoint endpoint = { nodePair.second->GetId(), disabledSlot->GetId() }; disabledEndpoints.insert(endpoint); } nodeLookUpMap.erase(nodePair.second->GetId()); [[maybe_unused]] size_t eraseCount = compiledGraphData.m_nodes.erase(nodePair.second); AZ_Assert(eraseCount == 1, "Failed to erase node from compiled graph data"); delete node; } } potentiallyDisabledNodes.clear(); } { auto connectionIter = compiledGraphData.m_connections.begin(); while (connectionIter != compiledGraphData.m_connections.end()) { AZ::Entity* connectionEntity = (*connectionIter); auto connection = AZ::EntityUtils::FindFirstDerivedComponent(connectionEntity); AZ_Assert(connection, "Connection is missing connection component."); if (connection == nullptr) { delete connectionEntity; connectionIter = compiledGraphData.m_connections.erase(connectionIter); continue; } ScriptCanvas::Endpoint targetEndpoint = connection->GetTargetEndpoint(); ScriptCanvas::Endpoint sourceEndpoint = connection->GetSourceEndpoint(); if (fullyRemovedEndpoints.count(targetEndpoint) > 0 || fullyRemovedEndpoints.count(sourceEndpoint) > 0 || deletedNodeEntities.count(targetEndpoint.GetNodeId()) > 0 || deletedNodeEntities.count(sourceEndpoint.GetNodeId()) > 0) { delete connectionEntity; connectionIter = compiledGraphData.m_connections.erase(connectionIter); } else { ++connectionIter; } } } return AZ::Success(compiledGraphData); } AZ::Outcome CompileVariableData(AZ::Entity* scriptCanvasEntity) { if (!scriptCanvasEntity) { return AZ::Failure(AZStd::string("Cannot compile variable data from a nullptr Script Canvas Entity")); } auto sourceGraphVariableManager = AZ::EntityUtils::FindFirstDerivedComponent(scriptCanvasEntity); if (!sourceGraphVariableManager) { return AZ::Failure(AZStd::string("Failed to find Editor Script Canvas Graph Variable Manager Component")); } return AZ::Success(*sourceGraphVariableManager->GetVariableData()); } int GetBuilderVersion() { // #functions2 remove-execution-out-hash include version from all library nodes, split fingerprint generation to relax Is Out of Data restriction when graphs only need a recompile return static_cast(BuilderVersion::Current) + static_cast(ScriptCanvas::GrammarVersion::Current) + static_cast(ScriptCanvas::RuntimeVersion::Current) ; } AZ::Outcome < AZ::Data::Asset, AZStd::string> LoadEditorAsset(AZStd::string_view filePath, AZ::Data::AssetId assetId, AZ::Data::AssetFilterCB assetFilterCB) { AZStd::shared_ptr assetDataStream = AZStd::make_shared(); // Read the asset into a memory buffer, then hand ownership of the buffer to assetDataStream { AZ::IO::FileIOStream stream(filePath.data(), AZ::IO::OpenMode::ModeRead); if (!AZ::IO::RetryOpenStream(stream)) { AZ_Warning(s_scriptCanvasBuilder, false, "CreateJobs for \"%s\" failed because the source file could not be opened.", filePath.data()); AZ::Failure(AZStd::string::format("Failed to load ScriptCavas asset: %s", filePath.data())); } AZStd::vector fileBuffer(stream.GetLength()); size_t bytesRead = stream.Read(fileBuffer.size(), fileBuffer.data()); if (bytesRead != stream.GetLength()) { AZ_Warning(s_scriptCanvasBuilder, false, "CreateJobs for \"%s\" failed because the source file could not be read.", filePath.data()); AZ::Failure(AZStd::string::format("Failed to load ScriptCavas asset: %s", filePath.data())); } assetDataStream->Open(AZStd::move(fileBuffer)); } ScriptCanvasEditor::ScriptCanvasAssetHandler editorAssetHandler; AZ::SerializeContext* context{}; AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationBus::Events::GetSerializeContext); AZ::Data::Asset asset; asset.Create(assetId); if (editorAssetHandler.LoadAssetData(asset, assetDataStream, assetFilterCB) != AZ::Data::AssetHandler::LoadResult::LoadComplete) { return AZ::Failure(AZStd::string::format("Failed to load ScriptCavas asset: %s", filePath.data())); } return AZ::Success(asset); } AZ::Outcome, AZStd::string> LoadEditorFunctionAsset(AZStd::string_view filePath) { AZStd::shared_ptr assetDataStream = AZStd::make_shared(); // Read the asset into a memory buffer, then hand ownership of the buffer to assetDataStream { AZ::IO::FileIOStream stream(filePath.data(), AZ::IO::OpenMode::ModeRead); if (!AZ::IO::RetryOpenStream(stream)) { return AZ::Failure(AZStd::string::format("File failed to open: %s", filePath.data())); } AZStd::vector fileBuffer(stream.GetLength()); size_t bytesRead = stream.Read(fileBuffer.size(), fileBuffer.data()); if (bytesRead != stream.GetLength()) { return AZ::Failure(AZStd::string::format("File failed to open: %s", filePath.data())); } assetDataStream->Open(AZStd::move(fileBuffer)); } AZ::IO::FileIOStream ioStream; if (!ioStream.Open(filePath.data(), AZ::IO::OpenMode::ModeRead)) { return AZ::Failure(AZStd::string::format("File failed to open: %s", filePath.data())); } ScriptCanvasEditor::ScriptCanvasFunctionAssetHandler editorAssetHandler; AZ::SerializeContext* context{}; AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationBus::Events::GetSerializeContext); AZ::Data::Asset asset; asset.Create(AZ::Data::AssetId(AZ::Uuid::CreateRandom())); if (editorAssetHandler.LoadAssetData(asset, assetDataStream, AZ::Data::AssetFilterCB{}) != AZ::Data::AssetHandler::LoadResult::LoadComplete) { return AZ::Failure(AZStd::string::format("Failed to load ScriptCavas Function asset: %s", filePath.data())); } return AZ::Success(asset); } ScriptCanvasEditor::Graph* PrepareSourceGraph(AZ::Entity* const buildEntity) { auto sourceGraph = AZ::EntityUtils::FindFirstDerivedComponent(buildEntity); if (!sourceGraph) { return nullptr; } // Remove nodes that do not have components, these could be versioning artifacts // or nodes that are missing due to a missing gem AZStd::vector nodesToRemove; for (auto* node : sourceGraph->GetGraphData()->m_nodes) { if (node->GetComponents().empty()) { // This is a problem, we can remove this node. AZ_TracePrintf("Script Canvas", "Removing node due to missing components: %s\nVerify that all gems that this script relies on are enabled", node->GetName().c_str()); nodesToRemove.push_back(node); } } for (AZ::Entity* nodeEntity : nodesToRemove) { sourceGraph->GetGraphData()->m_nodes.erase(nodeEntity); } // Remove these front-end components during build time to avoid trying to use components // the AP is not meant to use. for (auto* component : buildEntity->GetComponents()) { if (component->RTTI_GetType() == azrtti_typeid()) { buildEntity->RemoveComponent(component); } } ScriptCanvas::ScopedAuxiliaryEntityHandler entityHandler(buildEntity); if (buildEntity->GetState() == AZ::Entity::State::Init) { buildEntity->Activate(); } return sourceGraph; } AZ::Outcome ProcessTranslationJob(ProcessTranslationJobInput& input) { const bool saveRawLua{ true }; auto sourceGraph = PrepareSourceGraph(input.buildEntity); auto version = sourceGraph->GetVersion(); if (version.grammarVersion == ScriptCanvas::GrammarVersion::Initial || version.runtimeVersion == ScriptCanvas::RuntimeVersion::Initial) { return AZ::Failure(AZStd::string(ScriptCanvas::ParseErrors::SourceUpdateRequired)); } ScriptCanvas::Grammar::Request request; request.path = input.fullPath; request.name = input.fileNameOnly; request.namespacePath = input.namespacePath; request.scriptAssetId = input.assetID; request.graph = sourceGraph; request.rawSaveDebugOutput = ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile; request.printModelToConsole = ScriptCanvas::Grammar::g_printAbstractCodeModel; ScriptCanvas::Translation::Result translationResult = TranslateToLua(request); auto outcome = translationResult.IsSuccess(ScriptCanvas::Translation::TargetFlags::Lua); if (!outcome.IsSuccess()) { return AZ::Failure(outcome.GetError()); } const auto& translation = translationResult.m_translations.find(ScriptCanvas::Translation::TargetFlags::Lua)->second; AZ::IO::MemoryStream inputStream(translation.m_text.data(), translation.m_text.size()); AzFramework::ScriptCompileRequest compileRequest; AZStd::string vmFullPath = input.request->m_fullPath; AzFramework::StringFunc::Path::StripExtension(vmFullPath); vmFullPath += ScriptCanvas::Grammar::k_internalRuntimeSuffix; compileRequest.m_fullPath = vmFullPath; compileRequest.m_fileName = input.fileNameOnly; compileRequest.m_tempDirPath = input.request->m_tempDirPath; compileRequest.m_errorWindow = s_scriptCanvasBuilder; compileRequest.m_input = &inputStream; AzFramework::ConstructScriptAssetPaths(compileRequest); // compiles in input stream Lua in memory, writes output to disk constexpr bool writeAssetInfo{ true }; auto compileOutcome = AzFramework::CompileScriptAndSaveAsset(compileRequest, writeAssetInfo); if (!compileOutcome.IsSuccess()) { return AZ::Failure(compileOutcome.GetError()); }; // Interpreted Lua AssetBuilderSDK::JobProduct jobProduct; jobProduct.m_productFileName = compileRequest.m_destPath;; jobProduct.m_productAssetType = azrtti_typeid(); jobProduct.m_productSubID = AZ::ScriptAsset::CompiledAssetSubId; jobProduct.m_dependenciesHandled = true; input.response->m_outputProducts.push_back(AZStd::move(jobProduct)); const AZ::Data::AssetId scriptAssetId(input.assetID.m_guid, jobProduct.m_productSubID); const AZ::Data::Asset scriptAsset(scriptAssetId, jobProduct.m_productAssetType, {}); input.runtimeDataOut.m_script = scriptAsset; const ScriptCanvas::OrderedDependencies& orderedDependencies = translationResult.m_model->GetOrderedDependencies(); const ScriptCanvas::DependencyReport& dependencyReport = orderedDependencies.source; for (const auto& subgraphAssetID : orderedDependencies.orderedAssetIds) { const AZ::Data::AssetId dependentSubgraphAssetID(subgraphAssetID.m_guid, AZ_CRC("RuntimeData", 0x163310ae)); const AZ::Data::Asset subgraphAsset(dependentSubgraphAssetID, azrtti_typeid(), {}); input.runtimeDataOut.m_requiredAssets.push_back(subgraphAsset); } for (const auto& scriptEventAssetID : dependencyReport.scriptEventsAssetIds) { const AZ::Data::Asset subgraphAsset(scriptEventAssetID, azrtti_typeid(), {}); input.runtimeDataOut.m_requiredScriptEvents.push_back(subgraphAsset); } input.runtimeDataOut.m_input = AZStd::move(translation.m_runtimeInputs); input.runtimeDataOut.m_debugMap = AZStd::move(translation.m_debugMap); input.interfaceOut = AZStd::move(translation.m_subgraphInterface); return AZ::Success(); } AZ::Outcome SaveSubgraphInterface(ProcessTranslationJobInput& input, ScriptCanvas::SubgraphInterfaceData& subgraphInterface) { AZ::Data::Asset runtimeAsset; runtimeAsset.Create(AZ::Data::AssetId(input.assetID.m_guid, AZ_CRC("SubgraphInterface", 0xdfe6dc72))); runtimeAsset.Get()->SetData(subgraphInterface); AZStd::vector byteBuffer; AZ::IO::ByteContainerStream byteStream(&byteBuffer); bool runtimeCanvasSaved = input.assetHandler->SaveAssetData(runtimeAsset, &byteStream); if (!runtimeCanvasSaved) { return AZ::Failure(AZStd::string("Failed to save script canvas subgraph interface to object stream")); } AZ::IO::FileIOStream outFileStream(input.runtimeScriptCanvasOutputPath.data(), AZ::IO::OpenMode::ModeWrite); if (!outFileStream.IsOpen()) { return AZ::Failure(AZStd::string::format("Failed to open output file %s", input.runtimeScriptCanvasOutputPath.data())); } runtimeCanvasSaved = outFileStream.Write(byteBuffer.size(), byteBuffer.data()) == byteBuffer.size() && runtimeCanvasSaved; if (!runtimeCanvasSaved) { return AZ::Failure(AZStd::string::format("Unable to save script canvas subgraph interface file %s", input.runtimeScriptCanvasOutputPath.data())); } AssetBuilderSDK::JobProduct jobProduct; jobProduct.m_dependenciesHandled = true; jobProduct.m_productFileName = input.runtimeScriptCanvasOutputPath; jobProduct.m_productAssetType = azrtti_typeid(); jobProduct.m_productSubID = AZ_CRC("SubgraphInterface", 0xdfe6dc72); input.response->m_outputProducts.push_back(AZStd::move(jobProduct)); return AZ::Success(); } AZ::Outcome SaveRuntimeAsset(ProcessTranslationJobInput& input, ScriptCanvas::RuntimeData& runtimeData) { AZ::Data::Asset runtimeAsset; runtimeAsset.Create(AZ::Data::AssetId(input.assetID.m_guid, AZ_CRC("RuntimeData", 0x163310ae))); runtimeAsset.Get()->SetData(runtimeData); AZStd::vector byteBuffer; AZ::IO::ByteContainerStream byteStream(&byteBuffer); bool runtimeCanvasSaved = input.assetHandler->SaveAssetData(runtimeAsset, &byteStream); if (!runtimeCanvasSaved) { return AZ::Failure(AZStd::string("Failed to save runtime script canvas to object stream")); } AZ::IO::FileIOStream outFileStream(input.runtimeScriptCanvasOutputPath.data(), AZ::IO::OpenMode::ModeWrite); if (!outFileStream.IsOpen()) { return AZ::Failure(AZStd::string::format("Failed to open output file %s", input.runtimeScriptCanvasOutputPath.data())); } runtimeCanvasSaved = outFileStream.Write(byteBuffer.size(), byteBuffer.data()) == byteBuffer.size() && runtimeCanvasSaved; if (!runtimeCanvasSaved) { return AZ::Failure(AZStd::string::format("Unable to save runtime script canvas file %s", input.runtimeScriptCanvasOutputPath.data())); } AssetBuilderSDK::JobProduct jobProduct; // Scan our runtime input for any asset references // Store them as product dependencies AssetBuilderSDK::OutputObject(&runtimeData.m_input, azrtti_typeid(), input.runtimeScriptCanvasOutputPath, azrtti_typeid(), AZ_CRC("RuntimeData", 0x163310ae), jobProduct); // Output Object marks dependencies as handled. // We still have more to evaluate jobProduct.m_dependenciesHandled = false; jobProduct.m_dependencies.push_back({ runtimeData.m_script.GetId(), {} }); for (const auto& assetDependency : runtimeData.m_requiredAssets) { auto filterScripts = [](const AZ::Data::Asset& asset) { return asset.GetType() != azrtti_typeid(); }; if (AZ::Data::AssetManager::Instance().GetAsset(assetDependency.GetId(), assetDependency.GetType(), AZ::Data::AssetLoadBehavior::PreLoad)) { jobProduct.m_dependencies.push_back({ assetDependency.GetId(), {} }); } else { return AZ::Failure(AZStd::string::format("Unable load runtime Script Canvas dependency: %s", assetDependency.GetId().ToString().c_str())); } } for (const auto& scriptEventDependency : runtimeData.m_requiredScriptEvents) { if (AZ::Data::AssetManager::Instance().GetAsset(scriptEventDependency.GetId(), scriptEventDependency.GetType(), AZ::Data::AssetLoadBehavior::PreLoad)) { jobProduct.m_dependencies.push_back({ scriptEventDependency.GetId(), {} }); } else { return AZ::Failure(AZStd::string::format("Unable load runtime script event dependency: %s", scriptEventDependency.GetId().ToString().c_str())); } } jobProduct.m_dependenciesHandled = true; input.response->m_outputProducts.push_back(AZStd::move(jobProduct)); return AZ::Success(); } ScriptCanvas::Translation::Result TranslateToLua(ScriptCanvas::Grammar::Request& request) { request.translationTargetFlags = ScriptCanvas::Translation::TargetFlags::Lua; return ScriptCanvas::Translation::ParseAndTranslateGraph(request); } }