/* * 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 * */ #pragma once #include #include #include #include #include #include #include #include #include #include #include "DebugMap.h" #include "PrimitivesDeclarations.h" #include "PrimitivesExecution.h" namespace ScriptCanvas { class Graph; class Node; class Nodeable; class VariableData; struct GraphData; namespace Nodes { namespace Core { class Start; class FunctionDefinitionNode; } } namespace Grammar { class Context; /// This class parses a graph into abstract programming concepts for easier translation /// into C++, Lua, or whatever else would be needed class AbstractCodeModel : public AZStd::enable_shared_from_this { friend struct PrintMetaData; public: static bool RequiresCreationFunction(Data::eType type); static AbstractCodeModelConstPtr Parse(const Source& source, bool terminateOnError = false, bool terminateOnInternalError = false); AbstractCodeModel() = delete; AbstractCodeModel(const AbstractCodeModel&) = delete; AbstractCodeModel(const Source& source, bool terminateOnError = false, bool terminateOnInternalError = false); ~AbstractCodeModel(); void AddError(ExecutionTreeConstPtr execution, ValidationConstPtr&& error) const; AZStd::string AddTranslationVariableName(const AZStd::string& name) const; AZStd::optional> CheckUserNodeableDependencyConstructionIndex(VariableConstPtr nodeable) const; AZStd::vector CombineVariableLists (const AZStd::vector& constructionNodeables , const AZStd::vector>& constructionInputVariableIds , const AZStd::vector>& entityIds) const; AZStd::optional FindNodeableSimpleName(VariableConstPtr variable) const; const AZStd::pair* FindStaticVariable(VariableConstPtr) const; AZStd::vector GetAllExecutionRoots() const; const size_t* GetDebugInfoInIndex(ExecutionTreeConstPtr execution) const; const size_t* GetDebugInfoOutIndex(ExecutionTreeConstPtr execution, size_t index) const; const size_t* GetDebugInfoReturnIndex(ExecutionTreeConstPtr execution) const; const size_t* GetDebugInfoVariableAssignmentIndex(OutputAssignmentConstPtr output, size_t assignmentIndex) const; const size_t* GetDebugInfoVariableSetIndex(OutputAssignmentConstPtr output) const; const DebugSymbolMap& GetDebugMap() const; const OrderedDependencies& GetOrderedDependencies() const; EBusHandlingConstPtr GetEBusEventHandling(const Node*) const; AZStd::vector GetEBusHandlings() const; EventHandlingConstPtr GetEventHandling(const Node*) const; AZStd::vector GetEventHandlings() const; Grammar::ExecutionCharacteristics GetExecutionCharacteristics() const; AZStd::vector GetFunctions() const; VariableConstPtr GetImplicitVariable(ExecutionTreeConstPtr execution) const; const SubgraphInterface& GetInterface() const; const AZStd::unordered_set* GetLocalVariables(ExecutionTreeConstPtr execution) const; AZStd::vector GetNodeableParse() const; AZStd::sys_time_t GetParseDuration() const; const ParsedRuntimeInputs& GetRuntimeInputs() const; const Source& GetSource() const; const AZStd::string& GetSourceString() const; ExecutionTreeConstPtr GetStart() const; const AZStd::vector>& GetStaticVariablesNames() const; const AZStd::vector>& GetStaticVariablesNames(Grammar::ExecutionTreeConstPtr functionBlock) const; const ValidationResults::ValidationEventList& GetValidationEvents() const { return m_validationEvents; } VariableWriteHandlingConstPtr GetVariableHandling(const Slot* slot) const; VariableWriteHandlingConstSet GetVariableHandling(VariableConstPtr variable) const; const AZStd::vector& GetVariables() const; const AZStd::vector& GetVariablesUnused() const; bool IsErrorFree() const; // has modified data or handlers bool IsPerEntityDataRequired() const; // if only functions, constant data, but no with state operations on state or handlers, etc bool IsPureLibrary() const; // if any operations on state or handlers, etc bool IsUserNodeable() const; bool IsUserNodeable(VariableConstPtr variable) const; template AZStd::vector ToVariableList(const AZStd::vector>& source) const; private: ////////////////////////////////////////////////////////////////////////// // Internal parsing ////////////////////////////////////////////////////////////////////////// struct ReturnValueConnections { bool m_hasOtherConnections = false; AZStd::vector m_returnValuesOrReferences; }; void AddAllVariablesPreParse(); void AddDebugInformation(); void AddDebugInformation(ExecutionChild& execution); void AddDebugInformationFunctionDefinition(ExecutionTreePtr root); void AddDebugInformationIn(ExecutionTreePtr execution); void AddDebugInformationOut(ExecutionTreePtr execution); void AddDebugInfiniteLoopDetectionInLoop(ExecutionTreePtr execution); void AddDebugInfiniteLoopDetectionInHandler(ExecutionTreePtr execution); void AddError(const AZ::EntityId& nodeId, ExecutionTreeConstPtr execution, const AZStd::string_view error) const; VariablePtr AddMemberVariable(const Datum& datum, AZStd::string_view rawName); VariablePtr AddMemberVariable(const Datum& datum, AZStd::string_view rawName, const AZ::EntityId& sourceNodeId); VariablePtr AddMemberVariable(const Datum& datum, AZStd::string_view rawName, const VariableId& sourceVariableId); void AddValidation(ValidationConstPtr&& validation) const; VariablePtr AddVariable(const Datum& datum, AZStd::string_view rawName); VariablePtr AddVariable(const Datum& datum, AZStd::string_view rawName, const AZ::EntityId& sourceNodeId); VariablePtr AddVariable(const Datum& datum, AZStd::string_view rawName, const VariableId& sourceVariableId); VariablePtr AddVariable(const Data::Type& type, AZStd::string_view rawName); void AddVariable(VariablePtr variable); void CheckConversion(ConversionByIndex& conversion, VariableConstPtr source, size_t index, const Data::Type& targetType); void CheckConversions(OutputAssignmentPtr output); bool CheckCreateRoot(const Node& node); void CheckForKnownNullDereference(ExecutionTreeConstPtr parent, const ExecutionInput& input, const Slot& inputSlot); AZStd::string CheckUniqueInterfaceNames ( AZStd::string_view candidate , AZStd::string_view defaultName , AZStd::unordered_set& uniqueNames , const AZStd::unordered_set& nodelingsOut); AZStd::string CheckUniqueOutNames(AZStd::string_view outName, const AZStd::unordered_set& nodelingsOut); ExecutionTreePtr CreateChild(ExecutionTreePtr parent, const Node* node, const Slot* outSlot) const; ExecutionTreePtr CreateChildDebugMarker(ExecutionTreePtr parent) const; ExecutionTreePtr CreateChildPlaceHolder(ExecutionTreePtr parent) const; bool CreateEBusHandling(const Node& node); bool CreateEventHandling(const Node& node); bool CheckCreateNodeableParse(const Node& node); OutputAssignmentPtr CreateOutput(ExecutionTreePtr execution, const Slot& outputSlot, AZStd::string_view slotNameOverride, AZStd::string_view suffix); OutputAssignmentPtr CreateOutputAssignment(VariableConstPtr variable); bool CheckCreateUserEventHandling(const Node& node); bool CheckCreateUserFunctionDefinition(const Node& node); void ConvertAllMemberVariablesToLocal(ExecutionTreePtr newSource); void CreateUserFunctionDefinition(const Node& node, const Slot& entrySlot); bool CreateVariableWriteHandling(const Node& node); void CreateVariableWriteHandling(const Slot& slot, VariableConstPtr variable, bool startsConnected); OutputAssignmentConstPtr CreateOutputData(ExecutionTreePtr execution, ExecutionChild& executionChild, const Slot& output); void CullUnusedVariables(); // Execution cycle detection is done first, before parsing starts. // This way, infinite loops in the parser execution itself are prevented, and input // used from previous ACM execution nodes are properly handled. // Cycle detection done by performing a purely execution-centric traversal of the source graph, // which properly respects both the Ordered Sequencer node, and multiple Execution Outs connections // from a single slot. // Errors from poorly routed input are NOT detected here, and should not be, those are detected later. bool ExecutionContainsCyclesCheck(const Node& node, const Slot& outSlot); ReturnValueConnections FindAssignments(ExecutionTreeConstPtr execution, const Slot& output); VariableConstPtr FindVariable(const AZ::EntityId& sourceNodeId) const; VariableConstPtr FindVariable(const VariableId& sourceVariableId) const; VariableConstPtr FindReferencedVariableChecked(ExecutionTreeConstPtr execution, const Slot& slot) const; AZStd::pair FindReturnValueOnThread(ExecutionTreeConstPtr executionNode, const Node* node, const Slot* slot) const; AZStd::string GetOriginalVariableName(VariableConstPtr variable, const Node* node); AZStd::string GetOutputSlotNameOverride(ExecutionTreePtr execution, const Slot& outputSlot); VariableConstPtr GetReadVariable(ExecutionTreePtr execution); VariableConstPtr GetWrittenVariable(ExecutionTreePtr execution); bool IsActiveGraph() const; bool IsAutoConnectedLocalEBusHandler(const Node* node) const; AZStd::vector ModAllExecutionRoots(); AZStd::vector>& ModStaticVariablesNames(); AZStd::vector>& ModStaticVariablesNames(Grammar::ExecutionTreeConstPtr functionBlock); ExecutionTreePtr OpenScope(ExecutionTreePtr parent, const Node* node, const Slot* outSlot) const; void Parse(); bool Parse(const Node& node); void Parse(const AZStd::vector& startNodes); bool Parse(VariableWriteHandlingPtr variableHandling); void ParseAutoConnectedEBusHandlerVariables(); enum class FirstNode { Self, Parent }; VariableConstPtr ParseConnectedInputData(const Slot& inputSlot, ExecutionTreePtr executionWithInput, const EndpointsResolved& scriptCanvasNodesConnectedToInput, FirstNode firstNode); void ParseConstructionInputVariables(); ConstSlotsOutcome ParseDataOutSlots(ExecutionTreePtr execution, ExecutionChild& executionChild) const; void ParseDeactivation(); void ParseDebugInformation(ExecutionTreePtr execution); void ParseDependencies(const Node& node); void ParseDependenciesAssetIndicies(); void ParseEntityIdInput(ExecutionTreePtr execution); void ParseExecutionBreak(ExecutionTreePtr execution); void ParseExecutionCharacteristics(); void ParseExecutionCharacteristics(ExecutionTreePtr execution); void ParseExecutionCycleStatement(ExecutionTreePtr execution); ExecutionTreePtr ParseExecutionForEachLoop(ExecutionTreePtr execution, const Slot& loopSlot, const Slot& breakSlot); void ParseExecutionForEachStatement(ExecutionTreePtr execution); void ParseExecutionFunction(ExecutionTreePtr execution, const Slot& outSlot); void ParseExecutionFunctionRecurse(ExecutionTreePtr execution, ExecutionChild& child, const Slot& outSlot, const AZStd::pair& nodeAndSlot); void ParseExecutionIfStatement(ExecutionTreePtr execution); void ParseExecutionLogicalExpression(ExecutionTreePtr execution, Symbol symbol); void ParseExecutionLoop(ExecutionTreePtr execution); void ParseExecutionMultipleOutSyntaxSugar(ExecutionTreePtr execution, const EndpointsResolved& executionOutNodes, const AZStd::vector& outSlots); void ParseExecutionMultipleOutSyntaxSugarOfSequencNode(ExecutionTreePtr sequence); void ParseExecutionOnce(ExecutionTreePtr execution); void ParseExecutionRandomSwitchStatement(ExecutionTreePtr execution); void ParseExecutionSequentialChildren(ExecutionTreePtr execution); void ParseExecutionSwitchStatement(ExecutionTreePtr execution); void ParseExecutionTreeBody(ExecutionTreePtr execution, const Slot& outSlot); ExecutionTreePtr ParseExecutionTreeRoot(ExecutionTreePtr root); enum MarkLatent { No, Yes }; ExecutionTreePtr ParseExecutionTreeRoot(const Node& node, const Slot& outSlot, MarkLatent markLatent); void ParseExecutionTreeRoots(const Node& node); void ParseExecutionWhileLoop(ExecutionTreePtr execution); void ParseFunctionLocalStaticUseage(); void ParseImplicitVariables(const Node& node); void ParseInputData(ExecutionTreePtr execution); void ParseInputDatum(ExecutionTreePtr execution, const Slot& input); bool ParseInputThisPointer(ExecutionTreePtr execution); void ParseMetaData(ExecutionTreePtr execution); void ParseMultiExecutionPost(ExecutionTreePtr execution); void ParseMultiExecutionPre(ExecutionTreePtr execution); void ParseMultipleFunctionCallPost(ExecutionTreePtr execution); void ParseNodelingVariables(const Node& node, NodelingType nodelingType); void ParseOperatorArithmetic(ExecutionTreePtr execution); void ParseOutputData(ExecutionTreePtr execution, ExecutionChild& executionChild); void ParseOutputData(ExecutionTreePtr execution, ExecutionChild& executionChild, AZStd::vector& slots); void ParseOutputData(ExecutionTreePtr execution, ExecutionChild& executionChild, const Slot& output); void ParsePropertyExtractionsPost(ExecutionTreePtr execution); void ParsePropertyExtractionsPre(ExecutionTreePtr execution); void ParseReturnValue(ExecutionTreePtr execution, const Slot& returnValueSlot); void ParseReturnValue(ExecutionTreePtr execution, VariableConstPtr variable, const Slot* returnValueSlot); void ParseUserFunctionTopology(); void ParseUserIn(ExecutionTreePtr root, const Nodes::Core::FunctionDefinitionNode* nodeling); void ParseUserInData(ExecutionTreePtr execution, ExecutionChild& executionChild); void ParseUserLatent(ExecutionTreePtr leaf, const Nodes::Core::FunctionDefinitionNode* nodeling); void ParseUserLatentData(ExecutionTreePtr execution); void ParseUserOutCall(ExecutionTreePtr execution); void ParseUserOuts(); void ParseVariableHandling(); // tracks all local/member variable use, return true if uses a declared member variable bool ParseVariableUseAndPurity(ExecutionTreePtr execution); void PostParseErrorDetect(ExecutionTreePtr root); void PostParseProcess(ExecutionTreePtr root); void PruneNoOpChildren(const ExecutionTreePtr& execution); AZ::Outcome> RemoveChild(const ExecutionTreePtr& execution, const ExecutionTreeConstPtr& child); void RemoveFromTree(ExecutionTreePtr execution); struct ConnectionInPreviouslyExecutedScope { size_t m_childIndex = 0; size_t m_outputIndex = 0; ExecutionTreeConstPtr m_source; }; struct ConnectionsInPreviouslyExecutedScope { AZStd::vector m_connections; ExecutionTreePtr m_mostParent; }; struct ReturnValueDescription { AZStd::vector returnValues; size_t outCallCount = 1; }; struct UserInParseTopologyResult { bool addSingleOutToMap; bool addNewOutToLeavesWithout; bool addExplicitOutCalls; bool isSimpleFunction; }; void AccountForEBusConnectionControl(ExecutionTreePtr execution); template void AccountForEBusConnectionControl(ExecutionTreePtr execution, t_Handling handling) { // check for connection control method, which the slot must be at this point // update connection status in handling based on slot // later...make methods to track connected status in the thread to prevent infinite loops // (works for variables, may not work for ebus) const auto id = execution->GetId(); handling->m_isEverConnected = handling->m_isEverConnected || id.m_slot == id.m_node->GetEBusConnectSlot(); handling->m_isEverDisconnected = handling->m_isEverDisconnected || id.m_slot == id.m_node->GetEBusDisconnectSlot(); } void AddExecutionMapIn (UserInParseTopologyResult result , ExecutionTreeConstPtr root , const AZStd::vector& outCalls , AZStd::string_view defaultOutName , const Nodes::Core::FunctionDefinitionNode* nodelingIn , const AZStd::unordered_set& uniqueNodelingsOut); void AddExecutionMapLatentOut(const Nodes::Core::FunctionDefinitionNode& nodeling, ExecutionTreePtr out); void AddUserOutToLeaf(ExecutionTreePtr parent, ExecutionTreeConstPtr root, AZStd::string_view name); void AddPreviouslyExecutedScopeVariableToOutputAssignments(VariableConstPtr newInputVariable, const ConnectionsInPreviouslyExecutedScope& connectedInputInPreviouslyExecutedScope); void ConvertNamesToIdentifiers(); AZStd::vector> FindAllNodelingOuts(ExecutionTreePtr root) const; VariableConstPtr FindBoundVariable(GraphScopedVariableId variableId) const; ConnectionsInPreviouslyExecutedScope FindConnectedInputInPreviouslyExecutedScope(ExecutionTreePtr executionWithInput, const EndpointsResolved& scriptCanvasNodesConnectedToInput, FirstNode firstNode) const; bool FindConnectedInputInPreviouslyExecutedScopeRecurse(ConnectionsInPreviouslyExecutedScope& result, ExecutionTreeConstPtr outputSource, ExecutionTreePtr executionWithInput, const EndpointsResolved& scriptCanvasNodesConnectedToInput) const; VariableConstPtr FindConnectedInputInScope(ExecutionTreePtr executionWithInput, const EndpointsResolved& scriptCanvasNodesConnectedToInput, FirstNode firstNode) const; AZStd::vector> GetAllDeactivationVariables() const; bool InSimultaneousDataPath(const Node& node, const Slot& reference, const Slot& candidate) const; static UserInParseTopologyResult ParseUserInTolopology(size_t nodelingsOutCount, size_t leavesWithoutNodelingsCount); size_t m_outIndexCount = 0; size_t m_generatedIdCount = 0; ExecutionTreePtr m_start; AZStd::vector m_startNodes; ScopePtr m_graphScope; const Source m_source; OrderedDependencies m_orderedDependencies; AZStd::unordered_set m_userNodeables; AZStd::unordered_map m_dependencyByVariable; AZStd::vector m_variables; AZStd::vector m_variablesUnused; AZStd::vector m_possibleExecutionRoots; // true iff there are no internal errors and no error validation events bool m_isErrorFree = true; // for post parsing validation ValidationResults::ValidationEventList m_validationEvents; DebugSymbolMap m_debugMap; DebugSymbolMapReverse m_debugMapReverse; AZStd::sys_time_t m_parseDuration; AZStd::chrono::system_clock::time_point m_parseStartTime; EBusHandlingByNode m_ebusHandlingByNode; EventHandlingByNode m_eventHandlingByNode; ImplicitVariablesByNode m_implicitVariablesByNode; ControlVariablesBySourceNode m_controlVariablesBySourceNode; NodeableParseByNode m_nodeablesByNode; // owns the handling VariableHandlingBySlot m_variableWriteHandlingBySlot; // references the handling only VariableWriteHandlingByVariable m_variableWriteHandlingByVariable; // owns nothing AZStd::vector m_functions; // owns the execution in nodelings AZStd::unordered_map m_userInsThatRequireTopology; AZStd::unordered_map m_userOutsThatRequireTopology; AZStd::unordered_multimap m_outsMarkedLatent; AZStd::unordered_set m_outsMarkedImmediate; AZStd::unordered_set m_processedOuts; // the output slots of the In-Nodeling AZStd::unordered_map m_inputVariableByNodelingInSlot; // the output slots of the Out-Nodeling AZStd::unordered_map m_returnVariableByNodelingOutSlot; // the input slots of the Out-Nodeling AZStd::unordered_map m_outputVariableByNodelingOutSlot; AZStd::unordered_map m_returnValuesByUserFunctionDefinition; AZStd::unordered_map m_sourceVariableByDatum; AZStd::unordered_set m_subgraphStartCalls; AZStd::unordered_set m_activeDefaultObject; SubgraphInterface m_subgraphInterface; AZStd::unordered_set m_uniqueOutNames; AZStd::unordered_set m_uniqueInNames; AZStd::vector> m_staticVariableNames; AZStd::unordered_map>> m_staticVariableNamesByFunctionBlock; AZStd::unordered_map m_variableUseByExecution; VariableUseage m_variableUse; ParsedRuntimeInputs m_runtimeInputs; AZStd::vector FindUserImmediateInput(ExecutionTreePtr call) const; const ReturnValueDescription* FindUserImmediateOutput(ExecutionTreePtr call) const; AZStd::vector FindUserLatentOutput(ExecutionTreePtr call) const; AZStd::vector FindUserLatentReturnValues(ExecutionTreePtr call) const; bool IsSourceInScope(VariableConstPtr variable, VariableFlags::Scope scope) const; void MarkParseStart(); void MarkParseStop(); void ParseBranchOnResultFunctionCheck(ExecutionTreePtr execution); void ParseCheckedFunctionCheck(ExecutionTreePtr execution); void ParseExecutionIfStatementBooleanExpression(ExecutionTreePtr booleanExpressionExecution, AZStd::string executionName, LexicalScope lexicalScope); void ParseExecutionIfStatementInternalFunction(ExecutionTreePtr internalFunctionExecution); }; // class AbstractCodeModel template AZStd::vector AbstractCodeModel::ToVariableList(const AZStd::vector>& source) const { AZStd::vector variables; for (const auto& variable : source) { auto iter = AZStd::find_if (m_variables.begin() , m_variables.end() , [&](const auto& candidate) { return candidate->m_sourceVariableId == variable.first; }); if (iter != m_variables.end()) { variables.push_back(*iter); } } return variables; } } }