/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ScriptCanvasEditor { using namespace ScriptCanvas; // The runtime context (appropriately) always assumes that EntityIds are overridden, this step copies the values from the runtime data // over to the override data to simulate build step that does this when building prefabs AZ_INLINE void CopyAssetEntityIdsToOverrides(RuntimeDataOverrides& runtimeDataOverrides) { runtimeDataOverrides.m_entityIds.reserve(runtimeDataOverrides.m_runtimeAsset->GetData().m_input.m_entityIds.size()); for (auto& varEntityPar : runtimeDataOverrides.m_runtimeAsset->GetData().m_input.m_entityIds) { runtimeDataOverrides.m_entityIds.push_back(varEntityPar.second); } for (auto& dependency : runtimeDataOverrides.m_dependencies) { CopyAssetEntityIdsToOverrides(dependency); } } AZ_INLINE AZStd::vector LoadInterpretedDepencies(const ScriptCanvas::DependencySet& dependencySet) { AZStd::vector loadedAssets; if (!dependencySet.empty()) { for (auto& namespacePath : dependencySet) { if (namespacePath.empty()) { continue; } AZ_Assert(namespacePath.size() >= 3, "This functions assumes unit test dependencies are in the ScriptCanvas gem unit test folder"); AZStd::string originalPath = namespacePath[2].data(); for (size_t index = 3; index < namespacePath.size(); ++index) { originalPath += "/"; originalPath += namespacePath[index]; } if (originalPath.ends_with(Grammar::k_internalRuntimeSuffix) || originalPath.ends_with(Grammar::k_internalRuntimeSuffixLC)) { originalPath.resize(originalPath.size() - AZStd::string_view(Grammar::k_internalRuntimeSuffix).size()); } AZStd::string path = AZStd::string::format("%s/%s.scriptcanvas", k_unitTestDirPathRelative, originalPath.data()); LoadTestGraphResult loadResult = LoadTestGraph(path); AZ_Assert(loadResult.m_runtimeAsset, "failed to load dependent asset"); AZ::Outcome luaAssetOutcome = AZ::Failure(AZStd::string("lua asset creation for function failed")); ScriptCanvasEditor::EditorAssetConversionBus::BroadcastResult(luaAssetOutcome, &ScriptCanvasEditor::EditorAssetConversionBusTraits::CreateLuaAsset, loadResult.m_editorAsset, loadResult.m_graphPath); AZ_Assert(luaAssetOutcome.IsSuccess(), "failed to create Lua asset"); AZStd::string modulePath = namespacePath[0].data(); for (size_t index = 1; index < namespacePath.size(); ++index) { modulePath += "/"; modulePath += namespacePath[index]; } const ScriptCanvas::Translation::LuaAssetResult& luaAssetResult = luaAssetOutcome.GetValue(); // #functions2_recursive_unit_tests loadedAssets.push_back({ modulePath, loadResult.m_runtimeAsset, luaAssetResult, {} }); } } return loadedAssets; } AZ_INLINE DurationSpec DurationSpec::Seconds(float seconds) { DurationSpec spec; spec.m_spec = eDuration::Seconds; spec.m_seconds = seconds; return spec; } AZ_INLINE DurationSpec DurationSpec::Ticks(size_t ticks) { DurationSpec spec; spec.m_spec = eDuration::Ticks; spec.m_ticks = ticks; return spec; } AZ_INLINE LoadTestGraphResult LoadTestGraph(AZStd::string_view graphPath) { AZ::Data::Asset editorAsset; ScriptCanvasEditor::EditorAssetConversionBus::BroadcastResult(editorAsset, &ScriptCanvasEditor::EditorAssetConversionBusTraits::LoadAsset, graphPath); if (editorAsset.GetData()) { AZ::Outcome< AZ::Data::Asset, AZStd::string> assetOutcome = AZ::Failure(AZStd::string("asset creation failed")); ScriptCanvasEditor::EditorAssetConversionBus::BroadcastResult(assetOutcome, &ScriptCanvasEditor::EditorAssetConversionBusTraits::CreateRuntimeAsset, editorAsset); if (assetOutcome.IsSuccess()) { LoadTestGraphResult result; result.m_graphPath = graphPath; result.m_editorAsset = editorAsset; result.m_runtimeAsset = assetOutcome.GetValue(); result.m_entity = AZStd::make_unique("Loaded Graph"); return result; } } return {}; } AZ_INLINE void RunGraphImplementation(const RunGraphSpec& runGraphSpec, Reporters& reporters) { AZ_Assert(!reporters.empty(), "there must be at least one report"); for (auto& reporter : reporters) { if (runGraphSpec.runSpec.expectRuntimeFailure) { reporter.MarkExpectRuntimeFailure(); } reporter.SetExecutionMode(runGraphSpec.runSpec.execution); RunGraphImplementation(runGraphSpec, reporter); } } AZ_INLINE void RunEditorAsset(AZ::Data::Asset asset, Reporter& reporter, ScriptCanvas::ExecutionMode mode) { if (asset.IsReady()) { AZ::Data::AssetId assetId = asset.GetId(); AZ::Data::AssetId runtimeAssetId(assetId.m_guid, AZ_CRC("RuntimeData", 0x163310ae)); AZ::Data::Asset runtimeAsset; if (!runtimeAsset.Create(runtimeAssetId, true)) { return; } reporter.SetExecutionMode(mode); LoadTestGraphResult loadResult; loadResult.m_graphPath = asset.GetHint().c_str(); loadResult.m_editorAsset = asset; AZ::EntityId scriptCanvasId; loadResult.m_entity = AZStd::make_unique("Loaded test graph"); loadResult.m_runtimeAsset = runtimeAsset; RunGraphSpec runGraphSpec; runGraphSpec.dirPath = ""; runGraphSpec.graphPath = asset.GetHint().c_str(); runGraphSpec.runSpec.duration.m_spec = eDuration::Ticks; runGraphSpec.runSpec.duration.m_ticks = 10; runGraphSpec.runSpec.execution = mode; runGraphSpec.runSpec.release = true; runGraphSpec.runSpec.debug = runGraphSpec.runSpec.traced = false; RunGraphImplementation(runGraphSpec, loadResult, reporter); } } AZ_INLINE void RunGraphImplementation(const RunGraphSpec& runGraphSpec, Reporter& reporter) { TraceSuppressionBus::Broadcast(&TraceSuppressionRequests::SuppressPrintf, true); LoadTestGraphResult loadResult = LoadTestGraph(runGraphSpec.graphPath); TraceSuppressionBus::Broadcast(&TraceSuppressionRequests::SuppressPrintf, false); RunGraphImplementation(runGraphSpec, loadResult, reporter); } AZ_INLINE void RunGraphImplementation(const RunGraphSpec& runGraphSpec, LoadTestGraphResult& loadResult, Reporter& reporter) { ScriptCanvas::SystemRequestBus::Broadcast(&ScriptCanvas::SystemRequests::MarkScriptUnitTestBegin); if (loadResult.m_entity) { reporter.MarkGraphLoaded(); RuntimeData runtimeDataBuffer; AZStd::vector dependencyDataBuffer; AZStd::vector dependencies; if (runGraphSpec.runSpec.execution == ExecutionMode::Interpreted) { ScopedOutputSuppression outputSuppressor; AZ::Outcome luaAssetOutcome = AZ::Failure(AZStd::string("lua asset creation failed")); ScriptCanvasEditor::EditorAssetConversionBus::BroadcastResult(luaAssetOutcome, &ScriptCanvasEditor::EditorAssetConversionBusTraits::CreateLuaAsset, loadResult.m_editorAsset, loadResult.m_graphPath); reporter.MarkParseAttemptMade(); if (luaAssetOutcome.IsSuccess()) { const ScriptCanvas::Translation::LuaAssetResult& luaAssetResult = luaAssetOutcome.GetValue(); reporter.SetEntity(loadResult.m_entity->GetId()); reporter.SetDurations(luaAssetResult.m_parseDuration, luaAssetResult.m_translationDuration); reporter.MarkCompiled(); if (!reporter.IsProcessOnly()) { dependencies = LoadInterpretedDepencies(luaAssetResult.m_dependencies.source.userSubgraphs); RuntimeDataOverrides runtimeDataOverrides; runtimeDataOverrides.m_runtimeAsset = loadResult.m_runtimeAsset; if (!dependencies.empty()) { // #functions2_recursive_unit_tests eventually, this will need to be recursive, or the full asset handling system will need to be integrated into the testing framework // in order to test functionality with a dependency stack greater than 2 // load all script assets, and their dependencies, initialize statics on all those dependencies if it is the first time loaded { AZ::InMemoryScriptModules inMemoryModules; inMemoryModules.reserve(dependencies.size()); dependencyDataBuffer.resize(dependencies.size()); for (auto& dependency : dependencies) { inMemoryModules.emplace_back(dependency.path, dependency.luaAssetResult.m_scriptAsset); } AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequests::UseInMemoryRequireHook, inMemoryModules, AZ::ScriptContextIds::DefaultScriptContextId); } for (size_t index = 0; index < dependencies.size(); ++index) { auto& dependency = dependencies[index]; const ScriptCanvas::Translation::LuaAssetResult& depencyAssetResult = dependency.luaAssetResult; RuntimeDataOverrides dependencyRuntimeDataOverrides; dependencyRuntimeDataOverrides.m_runtimeAsset = dependency.runtimeAsset; runtimeDataOverrides.m_dependencies.push_back(dependencyRuntimeDataOverrides); RuntimeData& dependencyData = dependencyDataBuffer[index]; dependencyData.m_input = depencyAssetResult.m_runtimeInputs; dependencyData.m_debugMap = depencyAssetResult.m_debugMap; dependencyData.m_script = depencyAssetResult.m_scriptAsset; Execution::Context::InitializeActivationData(dependencyData); Execution::InitializeInterpretedStatics(dependencyData); } } loadResult.m_scriptAsset = luaAssetResult.m_scriptAsset; loadResult.m_runtimeAsset.Get()->GetData().m_script = loadResult.m_scriptAsset; loadResult.m_runtimeAsset.Get()->GetData().m_input = luaAssetResult.m_runtimeInputs; loadResult.m_runtimeAsset.Get()->GetData().m_debugMap = luaAssetResult.m_debugMap; loadResult.m_runtimeComponent = loadResult.m_entity->CreateComponent(); CopyAssetEntityIdsToOverrides(runtimeDataOverrides); loadResult.m_runtimeComponent->SetRuntimeDataOverrides(runtimeDataOverrides); Execution::Context::InitializeActivationData(loadResult.m_runtimeAsset->GetData()); Execution::InitializeInterpretedStatics(loadResult.m_runtimeAsset->GetData()); } else { reporter.SetGraph(AZ::Entity::MakeId()); } } } if (reporter.IsCompiled()) { if (reporter.IsProcessOnly()) { reporter.FinishReport(); } else { loadResult.m_entity->Init(); reporter.SetGraph(loadResult.m_runtimeComponent->GetScriptCanvasId()); { ScopedOutputSuppression outputSuppressor; if (runGraphSpec.runSpec.execution == ExecutionMode::Interpreted) { // make sure the functions have debug info, too if (reporter.GetExecutionConfiguration() == ExecutionConfiguration::Release) { ScriptCanvas::SystemRequestBus::Broadcast(&ScriptCanvas::SystemRequests::SetInterpretedBuildConfiguration, ScriptCanvas::BuildConfiguration::Release); } else if (reporter.GetExecutionConfiguration() == ExecutionConfiguration::Performance) { ScriptCanvas::SystemRequestBus::Broadcast(&ScriptCanvas::SystemRequests::SetInterpretedBuildConfiguration, ScriptCanvas::BuildConfiguration::Performance); } else { ScriptCanvas::SystemRequestBus::Broadcast(&ScriptCanvas::SystemRequests::SetInterpretedBuildConfiguration, ScriptCanvas::BuildConfiguration::Debug); } } loadResult.m_entity->Activate(); SimulateDuration(runGraphSpec.runSpec.duration); } if (runGraphSpec.runSpec.m_onPostSimulate) { AZStd::invoke(runGraphSpec.runSpec.m_onPostSimulate); } loadResult.m_entity->Deactivate(); reporter.CollectPerformanceTiming(); reporter.FinishReport(); loadResult.m_entity.reset(); } } if (runGraphSpec.runSpec.execution == ExecutionMode::Interpreted) { AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequests::ClearAssetReferences, loadResult.m_scriptAsset.GetId()); if (!dependencies.empty()) { AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequests::RestoreDefaultRequireHook, AZ::ScriptContextIds::DefaultScriptContextId); } AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequests::GarbageCollect); } } if (!reporter.IsReportFinished()) { reporter.FinishReport(); } ScriptCanvas::SystemRequestBus::Broadcast(&ScriptCanvas::SystemRequests::MarkScriptUnitTestEnd); } AZ_INLINE Reporters RunGraph(const RunGraphSpec& runGraphSpec) { Reporters reporters; if (runGraphSpec.runSpec.processOnly) { Reporter reporter; reporter.SetProcessOnly(runGraphSpec.runSpec.processOnly); reporters.push_back(reporter); } else { if (runGraphSpec.runSpec.release) { Reporter reporterRelease; reporterRelease.SetExecutionConfiguration(ExecutionConfiguration::Release); reporters.push_back(reporterRelease); Reporter reporterPeformance; reporterPeformance.SetExecutionConfiguration(ExecutionConfiguration::Performance); reporters.push_back(reporterPeformance); } if (runGraphSpec.runSpec.debug) { Reporter reporterDebug; reporterDebug.SetExecutionConfiguration(ExecutionConfiguration::Debug); reporters.push_back(reporterDebug); } if (runGraphSpec.runSpec.traced) { Reporter reporterTraced; reporterTraced.SetExecutionConfiguration(ExecutionConfiguration::Traced); reporters.push_back(reporterTraced); } } RunGraphImplementation(runGraphSpec, reporters); return reporters; } AZ_INLINE void Simulate(const DurationSpec& duration) { AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick); AZ::SystemTickBus::ExecuteQueuedEvents(); AZ::TickBus::Broadcast(&AZ::TickEvents::OnTick, duration.m_timeStep, AZ::ScriptTimePoint(AZStd::chrono::system_clock::now())); AZ::TickBus::ExecuteQueuedEvents(); } AZ_INLINE void SimulateDuration(const DurationSpec& duration) { switch (duration.m_spec) { case eDuration::InitialActivation: break; case eDuration::Seconds: SimulateSeconds(duration); break; case eDuration::Ticks: SimulateTicks(duration); break; default: break; } } AZ_INLINE void SimulateSeconds(const DurationSpec& duration) { float simulationDuration = duration.m_seconds; while (simulationDuration > 0.0f) { Simulate(duration); simulationDuration -= duration.m_timeStep; } } AZ_INLINE void SimulateTicks(const DurationSpec& duration) { size_t remainingTicks = duration.m_ticks; while (remainingTicks) { Simulate(duration); --remainingTicks; } } }