You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp

5329 lines
231 KiB
C++

/*
* 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 <AzCore/std/containers/unordered_map.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <ScriptCanvas/Core/Datum.h>
#include <ScriptCanvas/Core/EBusHandler.h>
#include <ScriptCanvas/Core/Graph.h>
#include <ScriptCanvas/Core/Nodeable.h>
#include <ScriptCanvas/Core/NodeableNode.h>
#include <ScriptCanvas/Core/NodeableNodeOverloaded.h>
#include <ScriptCanvas/Core/SubgraphInterfaceUtility.h>
#include <ScriptCanvas/Debugger/ValidationEvents/DataValidation/ScopedDataConnectionEvent.h>
#include <ScriptCanvas/Debugger/ValidationEvents/ParsingValidation/ParsingValidations.h>
#include <ScriptCanvas/Grammar/ParsingMetaData.h>
#include <ScriptCanvas/Libraries/Core/AzEventHandler.h>
#include <ScriptCanvas/Libraries/Core/EBusEventHandler.h>
#include <ScriptCanvas/Libraries/Core/FunctionDefinitionNode.h>
#include <ScriptCanvas/Libraries/Core/ExtractProperty.h>
#include <ScriptCanvas/Libraries/Core/ForEach.h>
#include <ScriptCanvas/Libraries/Core/FunctionCallNode.h>
#include <ScriptCanvas/Libraries/Core/FunctionCallNodeIsOutOfDate.h>
#include <ScriptCanvas/Libraries/Core/Method.h>
#include <ScriptCanvas/Libraries/Core/Start.h>
#include <ScriptCanvas/Translation/TranslationUtilities.h>
#include <ScriptCanvas/Variable/VariableData.h>
#include "AbstractCodeModel.h"
#include "ExecutionTraversalListeners.h"
#include "GrammarContextBus.h"
#include "ParsingUtilities.h"
#include "Primitives.h"
namespace AbstractCodeModelCpp
{
using namespace ScriptCanvas;
using namespace ScriptCanvas::Grammar;
AZStd::unordered_set< const Nodes::Core::FunctionDefinitionNode*> Intersection
( const AZStd::unordered_multimap<const Nodes::Core::FunctionDefinitionNode*, ExecutionTreePtr>& lhs
, const AZStd::unordered_set< const Nodes::Core::FunctionDefinitionNode*>& rhs)
{
AZStd::unordered_set< const Nodes::Core::FunctionDefinitionNode*> intersection;
for (auto candidate : lhs)
{
if (rhs.contains(candidate.first))
{
intersection.insert(candidate.first);
}
}
return intersection;
}
EndpointsResolved GetParentNodes(const Node* node)
{
EndpointsResolved resolved;
if (node)
{
auto slots = node->GetSlotsByType(CombinedSlotType::ExecutionIn);
for (auto slot : slots)
{
if (slot)
{
auto nodesInSlot = node->GetConnectedNodes(*slot);
resolved.insert(resolved.end(), nodesInSlot.begin(), nodesInSlot.end());
}
}
}
return resolved;
}
bool IsConnectedToUserInRecurse(const Node* node, AZStd::unordered_set<const Slot*>& previousPath);
bool IsConnectedToUserIn(const Node* node)
{
AZStd::unordered_set<const Slot*> previousPath;
return IsConnectedToUserInRecurse(node, previousPath);
}
bool IsConnectedToUserInRecurse(const Node* node, AZStd::unordered_set<const Slot*>& previousPath)
{
auto parents = GetParentNodes(node);
for (auto parent : parents)
{
if (previousPath.contains(parent.second))
{
continue;
}
previousPath.insert(parent.second);
auto nodeling = azrtti_cast<const Nodes::Core::FunctionDefinitionNode*>(parent.first);
if (nodeling && nodeling->IsExecutionEntry())
{
return true;
}
if (parent.first && IsConnectedToUserInRecurse(parent.first, previousPath))
{
return true;
}
}
return false;
}
}
namespace ScriptCanvas
{
namespace Grammar
{
using namespace Internal;
using namespace NodeCompatiliblity;
AbstractCodeModel::AbstractCodeModel(const Source& source, bool /*terminateOnError*/, bool /*terminateOnInternalError*/)
: m_source(source)
, m_graphScope(AZStd::make_shared<Scope>())
{
Parse();
}
AbstractCodeModel::~AbstractCodeModel()
{
if (m_start)
{
m_start->Clear();
m_start = nullptr;
}
m_graphScope = nullptr;
m_variables.clear();
for (auto iter : m_functions)
{
if (auto mutableIter = AZStd::const_pointer_cast<ExecutionTree>(iter))
{
mutableIter->Clear();
}
}
m_functions.clear();
m_userInsThatRequireTopology.clear();
m_userOutsThatRequireTopology.clear();
for (auto iter : m_ebusHandlingByNode)
{
if (iter.second)
{
iter.second->Clear();
}
}
m_ebusHandlingByNode.clear();
for (auto iter : m_eventHandlingByNode)
{
if (iter.second)
{
iter.second->Clear();
}
}
for (auto iter : m_nodeablesByNode)
{
if (auto mutableIter = AZStd::const_pointer_cast<NodeableParse>(iter.second))
{
mutableIter->Clear();
}
}
m_nodeablesByNode.clear();
for (auto iter : m_variableWriteHandlingBySlot)
{
if (auto mutableIter = AZStd::const_pointer_cast<VariableWriteHandling>(iter.second))
{
mutableIter->Clear();
}
}
m_variableWriteHandlingBySlot.clear();
m_variableWriteHandlingByVariable.clear();
m_userNodeables.clear();
}
void AbstractCodeModel::AccountForEBusConnectionControl(ExecutionTreePtr execution)
{
if (execution->GetSymbol() == Symbol::FunctionDefinition)
{
return;
}
const auto id = execution->GetId();
if (!(id.m_node && id.m_node->IsEventHandler()))
{
return;
}
auto ebusIter = m_ebusHandlingByNode.find(id.m_node);
if (ebusIter != m_ebusHandlingByNode.end())
{
AccountForEBusConnectionControl(execution, ebusIter->second);
}
else
{
for (auto slot : id.m_node->GetOnVariableHandlingDataSlots())
{
auto variableIter = m_variableWriteHandlingBySlot.find(slot);
if (variableIter != m_variableWriteHandlingBySlot.end())
{
AccountForEBusConnectionControl(execution, variableIter->second);
}
}
}
}
void AbstractCodeModel::AddAllVariablesPreParse()
{
auto& sourceVariables = m_source.m_variableData->GetVariables();
AZStd::set<const GraphVariable*, GraphVariable::Comparator> sortedVariables;
for (const auto& variablePair : sourceVariables)
{
if (variablePair.second.GetScope() != VariableFlags::Scope::FunctionReadOnly)
{
sortedVariables.insert(&variablePair.second);
}
if (auto datum = variablePair.second.GetDatum())
{
// #functions2 slot<->variable consider getting all variables from the UX variable manager, or from the ACM and looking them up in the variable manager for ordering
m_sourceVariableByDatum.insert(AZStd::make_pair(datum, &variablePair.second));
}
}
for (auto& sourceVariable : sortedVariables)
{
auto datum = sourceVariable->GetDatum();
AZ_Assert(datum != nullptr, "the datum must be valid");
// #functions2 slot<->variable check to verify if it is a member variable
auto variable = sourceVariable->GetScope() == VariableFlags::Scope::Graph
? AddMemberVariable(*datum, sourceVariable->GetVariableName(), sourceVariable->GetVariableId())
: AddVariable(*datum, sourceVariable->GetVariableName(), sourceVariable->GetVariableId());
variable->m_isExposedToConstruction = sourceVariable->IsComponentProperty();
// also, all nodeables with !empty editor data have to be exposed
// \todo future optimizations will involve checking equality against a default constructed object
}
}
void AbstractCodeModel::AddDebugInformation()
{
auto roots = ModAllExecutionRoots();
for (auto root : roots)
{
AddDebugInformationFunctionDefinition(root);
for (size_t index(0); index < root->GetChildrenCount(); ++index)
{
AddDebugInformation(root->ModChild(index));
}
}
}
void AbstractCodeModel::AddDebugInformation(ExecutionChild& execution)
{
if (execution.m_execution)
{
ParseDebugInformation(execution.m_execution);
for (size_t index(0); index < execution.m_execution->GetChildrenCount(); ++index)
{
AddDebugInformation(execution.m_execution->ModChild(index));
}
}
}
void AbstractCodeModel::AddDebugInformationFunctionDefinition(ExecutionTreePtr execution)
{
AddDebugInformationOut(execution);
if (execution->HasReturnValues())
{
DebugExecution returnValues;
returnValues.m_data.reserve(execution->GetReturnValueCount());
returnValues.m_namedEndpoint = execution->GetId().m_node->CreateNamedEndpoint(execution->GetId().m_slot->GetId());
for (size_t index(0); index < execution->GetReturnValueCount(); ++index)
{
returnValues.m_data.push_back(execution->GetReturnValue(index).second->m_sourceDebug);
}
m_debugMapReverse.m_return[execution] = m_debugMap.m_returns.size();
m_debugMap.m_returns.push_back(returnValues);
}
}
void AbstractCodeModel::AddDebugInformationIn(ExecutionTreePtr execution)
{
if (execution->GetSymbol() == Symbol::FunctionDefinition)
{
AddDebugInformationFunctionDefinition(execution);
}
else if (execution->GetId().m_node && execution->GetId().m_slot)
{
DebugExecution inDebug;
inDebug.m_namedEndpoint = execution->GetId().m_node->CreateNamedEndpoint(execution->GetId().m_slot->GetId());
inDebug.m_data.reserve(execution->GetInputCount());
for (size_t index(0); index < execution->GetInputCount(); ++index)
{
inDebug.m_data.push_back(execution->GetInput(index).m_sourceDebug);
}
m_debugMapReverse.m_in[execution] = m_debugMap.m_ins.size();
m_debugMap.m_ins.push_back(inDebug);
}
}
void AbstractCodeModel::AddDebugInformationOut(ExecutionTreePtr execution)
{
if (execution->GetId().m_node)
{
for (size_t index(0); index < execution->GetChildrenCount(); ++index)
{
auto& child = execution->GetChild(index);
DebugExecution debugOut;
debugOut.m_namedEndpoint = execution->GetId().m_node->CreateNamedEndpoint(child.m_slot ? child.m_slot->GetId() : SlotId());
debugOut.m_data.resize(child.m_output.size());
for (auto output : child.m_output)
{
auto outputAssignment = output.second;
if (output.first)
{
debugOut.m_data.push_back(DebugDataSource::FromSelfSlot(*output.first));
}
else
{
if (outputAssignment && outputAssignment->m_source)
{
debugOut.m_data.push_back(DebugDataSource::FromInternal(outputAssignment->m_source->m_datum.GetType()));
}
else
{
debugOut.m_data.push_back(DebugDataSource::FromInternal());
}
}
if (outputAssignment->m_source->m_sourceVariableId.IsValid())
{
DebugDataSource variableChange;
variableChange.m_slotDatumType = outputAssignment->m_source->m_datum.GetType();
variableChange.m_source = outputAssignment->m_source->m_sourceVariableId;
m_debugMapReverse.m_variableSets[outputAssignment] = m_debugMap.m_variables.size();
m_debugMap.m_variables.push_back(variableChange);
}
for (size_t index2(0); index2 < outputAssignment->m_assignments.size(); ++index2)
{
auto& assignment = outputAssignment->m_assignments[index2];
if (assignment->m_sourceVariableId.IsValid())
{
DebugDataSource variableChange;
variableChange.m_slotDatumType = assignment->m_datum.GetType();
variableChange.m_source = assignment->m_sourceVariableId;
m_debugMapReverse.m_assignments[outputAssignment].insert({ index2, m_debugMap.m_variables.size() });
m_debugMap.m_variables.push_back(variableChange);
}
}
}
m_debugMapReverse.m_out[execution].push_back(m_debugMap.m_outs.size());
m_debugMap.m_outs.push_back(debugOut);
}
}
}
void AbstractCodeModel::AddDebugInfiniteLoopDetectionInLoop(ExecutionTreePtr execution)
{
execution->MarkInfiniteLoopDetectionPoint();
auto counterName = m_graphScope->AddVariableName("loopIterationCounter");
m_implicitVariablesByNode.insert({ execution, AZStd::make_shared<Variable>(Datum(Data::Type::Number(), Datum::eOriginality::Original), counterName, TraitsFlags(0)) });
}
void AbstractCodeModel::AddDebugInfiniteLoopDetectionInHandler(ExecutionTreePtr execution)
{
execution->MarkInfiniteLoopDetectionPoint();
auto variable = AddMemberVariable(Datum(Data::Type::Number(), Datum::eOriginality::Original), "handlerIterationCounter");
variable->m_isDebugOnly = true;
m_implicitVariablesByNode.insert({ execution, variable });
}
void AbstractCodeModel::AddError(ExecutionTreeConstPtr execution, ValidationConstPtr&& error) const
{
if (execution && execution->GetRoot())
{
AZStd::string pretty;
Grammar::PrettyPrint(pretty, execution->GetRoot(), execution);
AZ_TracePrintf("Script Canvas", pretty.c_str());
}
AbstractCodeModel* mutableThis = const_cast<AbstractCodeModel*>(this);
mutableThis->m_isErrorFree = false;
AddValidation(AZStd::move(error));
}
void AbstractCodeModel::AddError(const AZ::EntityId& nodeId, ExecutionTreeConstPtr execution, const AZStd::string_view error) const
{
AddError(execution, aznew Internal::ParseError(nodeId, error));
}
void AbstractCodeModel::AddExecutionMapIn
( UserInParseTopologyResult /*result*/
, ExecutionTreeConstPtr root
, const AZStd::vector<ExecutionTreeConstPtr>& outCalls
, AZStd::string_view defaultOutName
, const Nodes::Core::FunctionDefinitionNode* nodelingIn
, const AZStd::unordered_set<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>& uniqueNodelingsOut)
{
In in;
SetDisplayAndParsedNameSafe(in, root->GetName());
in.sourceID = nodelingIn->GetIdentifier();
const auto defaultOutId = MakeDefaultOutId(in.sourceID);
const auto& functionInput = root->GetChild(0).m_output;
for (auto& input : functionInput)
{
in.inputs.push_back
({ GetOriginalVariableName(input.second->m_source, nodelingIn)
, input.second->m_source->m_name
, input.second->m_source->m_datum
, input.second->m_source->m_sourceVariableId });
}
if (!root->HasExplicitUserOutCalls())
{
// there is a single out, default or not
Out out;
if (outCalls.empty())
{
SetDisplayAndParsedName(out, defaultOutName);
out.sourceID = defaultOutId;
}
else
{
if (uniqueNodelingsOut.empty())
{
AddError(root->GetNodeId(), root, "Explicit Out call provided with no nodeling out");
return;
}
SetDisplayAndParsedName(out, outCalls[0]->GetName());
out.sourceID = (*uniqueNodelingsOut.begin())->GetIdentifier();
}
for (size_t returnValueIndex = 0; returnValueIndex < root->GetReturnValueCount(); ++returnValueIndex)
{
const auto& returnValueVariable = root->GetReturnValue(returnValueIndex).second->m_source;
out.outputs.push_back
({ GetOriginalVariableName(returnValueVariable, *uniqueNodelingsOut.begin())
, returnValueVariable->m_name
, returnValueVariable->m_datum.GetType()
, returnValueVariable->m_sourceVariableId });
}
in.outs.push_back(AZStd::move(out));
}
else
{
if (outCalls.size() < 2)
{
AddError(root->GetNodeId(), root, ScriptCanvas::ParseErrors::NotEnoughBranchesForReturn);
return;
}
for (auto& outCall : outCalls)
{
Out out;
const auto& outCallID = outCall->GetId();
auto nodelingCanBeNull = azrtti_cast<const Nodes::Core::FunctionDefinitionNode*>(outCallID.m_node);
if (nodelingCanBeNull)
{
SetDisplayAndParsedName(out, nodelingCanBeNull->GetDisplayName());
out.sourceID = nodelingCanBeNull->GetIdentifier();
}
else
{
SetDisplayAndParsedName(out, defaultOutName);
out.sourceID = defaultOutId;
}
for (size_t inputIndex = 0; inputIndex < outCall->GetInputCount(); ++inputIndex)
{
const auto& returnValueVariable = outCall->GetInput(inputIndex).m_value;
out.outputs.push_back
({ GetOriginalVariableName(returnValueVariable, outCallID.m_node)
, returnValueVariable->m_name
, returnValueVariable->m_datum.GetType()
, returnValueVariable->m_sourceVariableId });
}
AZStd::const_pointer_cast<ExecutionTree>(outCall)->SetOutCallIndex(m_outIndexCount);
++m_outIndexCount;
in.outs.push_back(AZStd::move(out));
}
}
m_subgraphInterface.AddIn(AZStd::move(in));
}
void AbstractCodeModel::AddExecutionMapLatentOut(const Nodes::Core::FunctionDefinitionNode& nodeling, ExecutionTreePtr outCall)
{
if (m_processedOuts.contains(&nodeling))
{
return;
}
m_processedOuts.insert(&nodeling);
Out out;
SetDisplayAndParsedName(out, nodeling.GetDisplayName());
out.sourceID = nodeling.GetIdentifier();
for (size_t inputIndex = 0; inputIndex < outCall->GetInputCount(); ++inputIndex)
{
const auto& inputVariable = outCall->GetInput(inputIndex).m_value;
out.outputs.push_back
({ GetOriginalVariableName(inputVariable, &nodeling)
, inputVariable->m_name
, inputVariable->m_datum.GetType()
, inputVariable->m_sourceVariableId });
}
for (size_t returnValueIndex = 0; returnValueIndex < outCall->GetReturnValueCount(); ++returnValueIndex)
{
const auto& returnValueVariable = outCall->GetReturnValue(returnValueIndex).second->m_source;
out.outputs.push_back
({ GetOriginalVariableName(returnValueVariable, &nodeling)
, returnValueVariable->m_name
, returnValueVariable->m_datum.GetType()
, returnValueVariable->m_sourceVariableId });
}
AZStd::const_pointer_cast<ExecutionTree>(outCall)->SetOutCallIndex(m_outIndexCount);
++m_outIndexCount;
m_subgraphInterface.AddLatent(AZStd::move(out));
}
void AbstractCodeModel::AddPreviouslyExecutedScopeVariableToOutputAssignments(VariableConstPtr newInputVariable, const ConnectionsInPreviouslyExecutedScope& connectedInputInPreviouslyExecutedScope)
{
for (const auto& connection : connectedInputInPreviouslyExecutedScope.m_connections)
{
OutputAssignmentPtr output = AZStd::const_pointer_cast<OutputAssignment>(connection.m_source->GetChild(connection.m_childIndex).m_output[connection.m_outputIndex].second);
output->m_assignments.push_back(newInputVariable);
}
}
VariablePtr AbstractCodeModel::AddMemberVariable(const Datum& datum, AZStd::string_view rawName)
{
auto variable = AddVariable(datum, MakeMemberVariableName(rawName));
variable->m_isMember = true;
return variable;
}
VariablePtr AbstractCodeModel::AddMemberVariable(const Datum& datum, AZStd::string_view rawName, const AZ::EntityId& sourceNodeId)
{
auto variable = AddVariable(datum, MakeMemberVariableName(rawName), sourceNodeId);
variable->m_isMember = true;
return variable;
}
VariablePtr AbstractCodeModel::AddMemberVariable(const Datum& datum, AZStd::string_view rawName, const VariableId& sourceVariableId)
{
auto variable = AddVariable(datum, MakeMemberVariableName(rawName), sourceVariableId);
variable->m_isMember = true;
return variable;
}
AZStd::string AbstractCodeModel::AddTranslationVariableName(const AZStd::string& name) const
{
return AZStd::const_pointer_cast<Scope>(m_graphScope)->AddVariableName(name);
}
void AbstractCodeModel::AddUserOutToLeaf(ExecutionTreePtr parent, ExecutionTreeConstPtr root, AZStd::string_view name)
{
ExecutionTreePtr out;
if (parent->GetSymbol() == Symbol::DebugInfoEmptyStatement)
{
parent->SetSymbol(Symbol::UserOut);
parent->SetName(name);
out = parent;
}
else
{
out = AZStd::make_shared<ExecutionTree>();
out->SetSymbol(Symbol::UserOut);
out->SetName(name);
out->SetParent(parent);
if (parent->GetChildrenCount() == 0)
{
parent->AddChild({ nullptr, {}, out });
}
else
{
AZ_Assert(parent->GetChildrenCount() == 1, "should only be one child");
AZ_Assert(parent->ModChild(0).m_execution == nullptr, "memory leak risk");
parent->ModChild(0).m_execution = out;
}
}
}
void AbstractCodeModel::AddValidation(ValidationConstPtr&& validation) const
{
AbstractCodeModel* mutableThis = const_cast<AbstractCodeModel*>(this);
mutableThis->m_validationEvents.emplace_back(validation);
ValidationResults results;
results.AddValidationEvent(validation.get());
}
void AbstractCodeModel::AddVariable(VariablePtr variable)
{
variable->m_name = m_graphScope->AddVariableName(variable->m_name);
m_variables.push_back(variable);
}
VariablePtr AbstractCodeModel::AddVariable(const Datum& datum, AZStd::string_view rawName)
{
auto variable = AZStd::make_shared<Variable>(datum, rawName, TraitsFlags(0));
AddVariable(variable);
return variable;
}
VariablePtr AbstractCodeModel::AddVariable(const Datum& datum, AZStd::string_view rawName, const AZ::EntityId& sourceNodeId)
{
auto variable = AddVariable(datum, rawName);
variable->m_nodeableNodeId = sourceNodeId;
return variable;
}
VariablePtr AbstractCodeModel::AddVariable(const Datum& datum, AZStd::string_view rawName, const VariableId& sourceVariableId)
{
auto variable = AddVariable(datum, rawName);
variable->m_sourceVariableId = sourceVariableId;
return variable;
}
VariablePtr AbstractCodeModel::AddVariable(const Data::Type& type, AZStd::string_view rawName)
{
return AddVariable(Datum(type), rawName);
}
void AbstractCodeModel::CheckConversion(ConversionByIndex& conversion, VariableConstPtr source, size_t index, const Data::Type& targetType)
{
const Data::Type& sourceType = source->m_datum.GetType();
if (!sourceType.IS_A(targetType) && sourceType.IsConvertibleTo(targetType))
{
conversion.insert({ index, targetType });
}
}
void AbstractCodeModel::CheckConversions(OutputAssignmentPtr output)
{
output->m_sourceConversions.clear();
for (size_t i = 0; i < output->m_assignments.size(); ++i)
{
CheckConversion(output->m_sourceConversions, output->m_source, i, output->m_assignments[i]->m_datum.GetType());
}
}
bool AbstractCodeModel::CheckCreateRoot(const Node& node)
{
return CheckCreateNodeableParse(node)
|| CheckCreateUserEventHandling(node)
|| CheckCreateUserFunctionDefinition(node);
}
AZStd::string AbstractCodeModel::CheckUniqueInterfaceNames
( AZStd::string_view candidate
, AZStd::string_view defaultName
, AZStd::unordered_set<AZStd::string>& uniqueNames
, const AZStd::unordered_set<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>& nodelingsOut)
{
if (nodelingsOut.size() == 1 && (*nodelingsOut.begin())->GetDisplayName() == candidate)
{
return (*nodelingsOut.begin())->GetDisplayName();
}
if (!uniqueNames.contains(candidate))
{
uniqueNames.insert(candidate);
return candidate;
}
if (!uniqueNames.contains(defaultName))
{
uniqueNames.insert(defaultName);
return defaultName;
}
size_t index = uniqueNames.size();
AZStd::string numberedOut = AZStd::string::format("%s %zu", defaultName.data(), index);
while (uniqueNames.contains(numberedOut))
{
++index;
numberedOut = AZStd::string::format("%s %zu", defaultName.data(), index);
}
uniqueNames.insert(numberedOut);
return numberedOut;
}
AZStd::string AbstractCodeModel::CheckUniqueOutNames(AZStd::string_view displayName, const AZStd::unordered_set<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>& nodelingsOut)
{
return CheckUniqueInterfaceNames(displayName, "Out", m_uniqueOutNames, nodelingsOut);
}
AZStd::optional<AZStd::pair<size_t, DependencyInfo>> AbstractCodeModel::CheckUserNodeableDependencyConstructionIndex(VariableConstPtr variable) const
{
auto iter = m_dependencyByVariable.find(variable);
if (iter != m_dependencyByVariable.end())
{
for (size_t index = 0; index != m_orderedDependencies.orderedAssetIds.size(); ++index)
{
if (iter->second.assetId == m_orderedDependencies.orderedAssetIds[index])
{
return AZStd::make_pair(index, iter->second);
}
}
}
return {};
}
AZStd::vector<Grammar::VariableConstPtr> AbstractCodeModel::CombineVariableLists
( const AZStd::vector<Nodeable*>& constructionNodeables
, const AZStd::vector<AZStd::pair<VariableId, Datum>>& constructionInputVariables
, const AZStd::vector<AZStd::pair<VariableId, Data::EntityIDType>>& entityIds) const
{
AZStd::vector<Grammar::VariableConstPtr> variables;
for (const auto& nodeable : constructionNodeables)
{
const void* nodeableAsVoidPtr = nodeable;
auto iter = AZStd::find_if
( m_nodeablesByNode.begin()
, m_nodeablesByNode.end()
, [&](const auto& candidate)
{
return candidate.second->m_nodeable->m_datum.GetAsDanger() == nodeableAsVoidPtr;
});
if (iter != m_nodeablesByNode.end())
{
variables.push_back(iter->second->m_nodeable);
}
}
auto constructionVariables = ToVariableList(constructionInputVariables);
variables.insert(variables.end(), constructionVariables.begin(), constructionVariables.end());
for (const auto& variable : entityIds)
{
auto iter = AZStd::find_if( m_variables.begin(), m_variables.end(), [&](const auto& candidate) {
if (candidate->m_datum.GetType() == Data::Type::EntityID())
{
bool isVariableIdMatch = candidate->m_sourceVariableId == variable.first;
auto entityId = candidate->m_datum.template GetAs<Data::EntityIDType>();
if (entityId)
{
return isVariableIdMatch && *entityId == variable.second;
}
else
{
return isVariableIdMatch;
}
}
else
{
return false;
}
});
if (iter != m_variables.end())
{
variables.push_back(*iter);
}
}
return variables;
}
void AbstractCodeModel::ConvertNamesToIdentifiers()
{
class ConvertListener : public ExecutionTreeTraversalListener
{
protected:
void Evaluate(ExecutionTreeConstPtr node, const Slot*, int) override
{
if (node->GetSymbol() != Symbol::UserOut
&& azrtti_istypeof<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>(node->GetId().m_node))
{
AZStd::const_pointer_cast<ExecutionTree>(node)->ConvertNameToIdentifier();
}
}
};
ConvertListener convertListener;
TraverseTree(*this, convertListener);
}
ExecutionTreePtr AbstractCodeModel::CreateChild(ExecutionTreePtr parent, const Node* node, const Slot* slot) const
{
ExecutionTreePtr child = AZStd::make_shared<ExecutionTree>();
child->SetParent(parent);
child->SetId({node, slot});
child->SetScope(parent ? parent->ModScope() : m_graphScope);
return child;
}
ExecutionTreePtr AbstractCodeModel::CreateChildDebugMarker(ExecutionTreePtr parent) const
{
ExecutionTreePtr child = AZStd::make_shared<ExecutionTree>();
child->SetParent(parent);
child->SetScope(parent ? parent->ModScope() : m_graphScope);
child->SetSymbol(Symbol::DebugInfoEmptyStatement);
return child;
}
ExecutionTreePtr AbstractCodeModel::CreateChildPlaceHolder(ExecutionTreePtr parent) const
{
ExecutionTreePtr child = AZStd::make_shared<ExecutionTree>();
child->SetParent(parent);
child->SetScope(parent ? parent->ModScope() : m_graphScope);
child->SetSymbol(Symbol::PlaceHolderDuringParsing);
return child;
}
bool AbstractCodeModel::CreateEBusHandling(const Node& node)
{
auto ebusHandling = AZStd::make_shared<EBusHandling>();
ebusHandling->m_ebusName = node.GetEBusName();
ebusHandling->m_handlerName = m_graphScope->AddVariableName(AZStd::string::format("%sHandler", ebusHandling->m_ebusName.data()));
auto addressSlot = node.GetEBusConnectAddressSlot();
VariableConstPtr startingAddressVariable = addressSlot && addressSlot->IsVariableReference() ? FindVariable(addressSlot->GetVariableReference()) : nullptr;
ebusHandling->m_isAddressed = node.IsEBusAddressed();
if (ebusHandling->m_isAddressed)
{
if (addressSlot == nullptr)
{
AddError(node.GetEntityId(), nullptr, "Missing slot for ebus event");
return false;
}
if (addressSlot->IsVariableReference())
{
if (startingAddressVariable != nullptr)
{
ebusHandling->m_startingAdress = startingAddressVariable;
}
else
{
AddError(node.GetEntityId(), nullptr, ParseErrors::MissingVariableForEBusHandlerAddress);
return false;
}
}
}
if (node.IsAutoConnected())
{
if (ebusHandling->m_isAddressed)
{
ebusHandling->m_startsConnected = Data::IsValueType(addressSlot->GetDataType()) || startingAddressVariable != nullptr;
}
else
{
ebusHandling->m_startsConnected = true;
}
}
ebusHandling->m_isAutoConnected = node.IsAutoConnected();
if (ebusHandling->m_isAddressed && !addressSlot->IsVariableReference() && (ebusHandling->m_startsConnected || ebusHandling->m_isAutoConnected))
{
auto startAddressDatum = node.GetHandlerStartAddress();
if (!startAddressDatum)
{
AddError(node.GetEntityId(), nullptr, ParseErrors::MissingVariableForEBusHandlerAddressConnected);
return false;
}
VariablePtr startingAddress = AddMemberVariable(*startAddressDatum, AZStd::string::format("%sAddress", ebusHandling->m_ebusName.data()).data(), node.GetEntityId());
ebusHandling->m_startingAdress = startingAddress;
}
ebusHandling->m_node = &node;
m_ebusHandlingByNode.emplace(&node, ebusHandling);
return true;
}
bool AbstractCodeModel::CreateEventHandling(const Node& node)
{
const auto connectSlot = AzEventHandlerProperty::GetConnectSlot(&node);
if (!connectSlot)
{
AddError(node.GetEntityId(), nullptr, ParseErrors::EventNodeMissingConnectSlot);
return false;
}
const EndpointsResolved connectedEndpoints = node.GetConnectedNodes(*connectSlot);
if (connectedEndpoints.empty())
{
return false;
}
if (connectedEndpoints.size() > 1)
{
AddError(node.GetEntityId(), nullptr, ParseErrors::EventNodeConnectCallMalformed);
return false;
}
auto azEventNode = azrtti_cast<const ScriptCanvas::Nodes::Core::AzEventHandler*>(&node);
if (!azEventNode)
{
AddError(node.GetEntityId(), nullptr, ParseErrors::BadEventHandlingAccounting);
return false;
}
auto eventInputSlot = azEventNode->GetEventInputSlot();
if (!eventInputSlot)
{
AddError(node.GetEntityId(), nullptr, ParseErrors::EventNodeMissingConnectEventInputSlot);
return false;
}
auto inputHandlerDatum = eventInputSlot->FindDatum();
if (!inputHandlerDatum)
{
AddError(node.GetEntityId(), nullptr, ParseErrors::EventNodeMissingConnectEventInputMissingVariableDatum);
return false;
}
const EndpointResolved& endpoint = connectedEndpoints.front();
auto eventHandling = AZStd::make_shared<EventHandling>();
eventHandling->m_eventName = node.GetNodeName();
eventHandling->m_eventNode = endpoint.first;
eventHandling->m_eventSlot = endpoint.second;
auto name = AZStd::string::format("%sHandler", eventHandling->m_eventName.data());
eventHandling->m_handler = AddMemberVariable(*inputHandlerDatum, name, node.GetEntityId());
eventHandling->m_handler->m_requiresNullCheck = true;
eventHandling->m_handler->m_initializeAsNull = true;
m_eventHandlingByNode.emplace(&node, eventHandling);
return true;
}
bool AbstractCodeModel::CheckCreateNodeableParse(const Node& node)
{
if (auto nodeableNode = azrtti_cast<const ScriptCanvas::Nodes::NodeableNode*>(&node))
{
if (const Nodeable* nodeable = nodeableNode->GetNodeable())
{
Datum nodeableDatum(Data::Type::BehaviorContextObject(azrtti_typeid(nodeable)), Datum::eOriginality::Copy, reinterpret_cast<const void*>(nodeable), azrtti_typeid(nodeable));
auto nodeableVariable = AddMemberVariable(nodeableDatum, nodeable->RTTI_GetTypeName(), node.GetEntityId());
auto nodeableParse = AZStd::make_shared<NodeableParse>();
nodeableVariable->m_isExposedToConstruction = true;
nodeableParse->m_nodeable = nodeableVariable;
// iterate through all on variable handlings
for (auto executionSlot : node.GetOnVariableHandlingExecutionSlots())
{
const auto name = m_graphScope->AddFunctionName(AZStd::string::format("On%s", executionSlot->GetName().data()));
ExecutionTreePtr onVariableExecution = OpenScope(nullptr, &node, nullptr);
onVariableExecution->SetSymbol(Symbol::FunctionDefinition);
onVariableExecution->SetName(name);
ExecutionTreePtr onInputChangeExecution = CreateChild(onVariableExecution, &node, executionSlot);
ParseInputThisPointer(onInputChangeExecution);
auto dataInOutcomes = nodeableNode->GetDataInSlotsByExecutionIn(*nodeableNode->GetSlotExecutionMap(), *executionSlot);
AZ_Assert(dataInOutcomes.IsSuccess() && dataInOutcomes.GetValue().size() == 1, "Should have only one input per on variable handling.");
auto dataSlot = dataInOutcomes.GetValue()[0];
auto datum = node.FindDatum(dataSlot->GetId());
VariablePtr variable = AddMemberVariable(*datum, dataSlot->GetName(), node.GetEntityId());
CreateVariableWriteHandling(*dataSlot, variable, node.IsAutoConnected());
onInputChangeExecution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() });
FunctionCallDefaultMetaData metaData;
metaData.PostParseExecutionTreeBody(*this, onInputChangeExecution);
nodeableParse->m_onInputChanges.push_back(onInputChangeExecution);
onVariableExecution->AddChild({ nullptr, {}, onInputChangeExecution });
VariableWriteHandlingPtr onVariableHandling = AZStd::const_pointer_cast<VariableWriteHandling>(GetVariableHandling(dataSlot));
AZ_Assert(onVariableHandling != nullptr, "failure to create variable handling for ebus address");
onVariableHandling->m_function = onVariableExecution;
}
m_nodeablesByNode.emplace(&node, nodeableParse);
return true;
}
else
{
if (azrtti_istypeof<ScriptCanvas::Nodes::NodeableNodeOverloaded*>(&node))
{
// todo Add node to these errors
AddError(nullptr, ValidationConstPtr(aznew Internal::ParseError(node.GetEntityId(), AZStd::string::format("%s: %s", ParseErrors::NodeableNodeOverloadAmbiguous, node.GetDebugName().data()))));
}
else
{
// todo Add node to these errors
AddError(nullptr, ValidationConstPtr(aznew Internal::ParseError(node.GetEntityId(), AZStd::string::format("%s: %s", ParseErrors::NodeableNodeDidNotConstructInternalNodeable, node.GetDebugName().data()))));
}
}
}
else if (auto functionCallNode = azrtti_cast<const Nodes::Core::FunctionCallNode*>(&node))
{
auto subgraphInterface = functionCallNode->GetSubgraphInterface();
auto requiresCtorParamsForDependencies = subgraphInterface && subgraphInterface->RequiresConstructionParametersForDependencies();
auto requiresCtorParams = subgraphInterface && subgraphInterface->RequiresConstructionParameters();
if (requiresCtorParams)
{
m_subgraphInterface.MarkRequiresConstructionParametersForDependencies();
}
// #functions2 pure on graph start nodes with dependencies can only by added to the graph as variables
if (!functionCallNode->IsPure())
{
Datum nodeableDatum(Data::Type::BehaviorContextObject(azrtti_typeid<Nodeable>()), Datum::eOriginality::Copy);
auto nodeableVariable = AddMemberVariable(nodeableDatum, functionCallNode->GetInterfaceName(), node.GetEntityId());
auto nodeableParse = AZStd::make_shared<NodeableParse>();
nodeableVariable->m_isExposedToConstruction = false;
nodeableParse->m_nodeable = nodeableVariable;
nodeableParse->m_simpleName = subgraphInterface->GetName();
m_nodeablesByNode.emplace(&node, nodeableParse);
m_userNodeables.insert(nodeableVariable);
m_dependencyByVariable.emplace(nodeableVariable, DependencyInfo{ functionCallNode->GetAssetId(), requiresCtorParams, requiresCtorParamsForDependencies });
return true;
}
}
return false;
}
OutputAssignmentConstPtr AbstractCodeModel::CreateOutputData(ExecutionTreePtr execution, ExecutionChild& out, const Slot& outputSlot)
{
/// \note never called on a branch
if (execution->GetSymbol() == Symbol::FunctionDefinition)
{
// Node output is input data to a function definition
OutputAssignmentPtr output = CreateOutput(execution, outputSlot, {}, "input");
if (auto variable = FindReferencedVariableChecked(execution, outputSlot))
{
output->m_assignments.push_back(variable);
CheckConversions(output);
}
return output;
}
ReturnValueConnections connections = FindAssignments(execution, outputSlot);
// get/set methods
if (IsVariableSet(execution) && !IsPropertyExtractionSlot(execution, &outputSlot))
{
AZ_Assert(out.m_output.size() == 1, "the output for Get/Set should already have been supplied");
if (!connections.m_returnValuesOrReferences.empty())
{
// return values must get assigned immediately, other inputs will simply read the output
OutputAssignmentPtr output = AZStd::const_pointer_cast<OutputAssignment>(out.m_output[0].second);
output->m_assignments.insert(output->m_assignments.end(), connections.m_returnValuesOrReferences.begin(), connections.m_returnValuesOrReferences.end());
CheckConversions(output);
}
// output already created for Set
return nullptr;
}
if (!connections.m_returnValuesOrReferences.empty())
{
if (connections.m_hasOtherConnections || connections.m_returnValuesOrReferences.size() > 1)
{
auto outputThread = CreateOutput(execution, outputSlot, GetOutputSlotNameOverride(execution, outputSlot), "output");
// this output will be written, and then assigned the assignments
outputThread->m_assignments = AZStd::move(connections.m_returnValuesOrReferences);
CheckConversions(outputThread);
return outputThread;
}
else
{
// this output only needs to be directly written to the assignment
return CreateOutputAssignment(connections.m_returnValuesOrReferences[0]);
}
}
else if (connections.m_hasOtherConnections)
{
// no return variable, but connected output which may be read by other inputs
return CreateOutput(execution, outputSlot, GetOutputSlotNameOverride(execution, outputSlot), "output");
}
return CreateOutput(execution, outputSlot, GetOutputSlotNameOverride(execution, outputSlot), "");
}
OutputAssignmentPtr AbstractCodeModel::CreateOutput(ExecutionTreePtr execution, const Slot& outputSlot, AZStd::string_view slotNameOverride, AZStd::string_view suffix)
{
auto output = AZStd::make_shared<Variable>();
output->m_source = execution;
auto outputSlotDatum = outputSlot.FindDatum();
// If slot has corresponding datum, use original one
if (outputSlotDatum && outputSlotDatum->GetType().IsValid())
{
output->m_datum = *outputSlotDatum;
}
else
{
output->m_datum = AZStd::move(Datum(outputSlot.GetDataType(), Datum::eOriginality::Copy));
}
output->m_sourceSlotId = outputSlot.GetId();
output->m_name = execution->ModScope()->AddVariableName(slotNameOverride.empty() ? outputSlot.GetName().c_str() : slotNameOverride.data(), suffix);
output->m_isUnused = !execution->GetId().m_node->IsConnected(outputSlot); // \todo check for variable read/write names
return CreateOutputAssignment(output);
}
OutputAssignmentPtr AbstractCodeModel::CreateOutputAssignment(VariableConstPtr output)
{
auto outputPtr = AZStd::make_shared<OutputAssignment>();
outputPtr->m_source = output;
return outputPtr;
}
bool AbstractCodeModel::CheckCreateUserEventHandling(const Node& node)
{
const EventHandingType handlerType = CheckEventHandlingType(node);
switch (handlerType)
{
case EventHandingType::EBus:
return CreateEBusHandling(node);
case EventHandingType::Event:
return CreateEventHandling(node);
case EventHandingType::VariableWrite:
return CreateVariableWriteHandling(node);
default:
AZ_Assert(handlerType == EventHandingType::Count, "new event handling type added but not handled");
return false;
}
}
void AbstractCodeModel::ConvertAllMemberVariablesToLocal(ExecutionTreePtr startNode)
{
auto useageIter = m_variableUseByExecution.find(startNode);
if (useageIter == m_variableUseByExecution.end())
{
useageIter = m_variableUseByExecution.emplace(startNode, VariableUseage()).first;
}
for (auto constVariable : m_variables)
{
auto variable = AZStd::const_pointer_cast<Variable>(constVariable);
if (variable->m_isMember && !variable->m_nodeableNodeId.IsValid())
{
variable->m_isMember = false;
variable->m_source = startNode;
useageIter->second.localVariables.insert(constVariable);
useageIter->second.memberVariables.erase(constVariable);
m_variableUse.localVariables.insert(constVariable);
m_variableUse.memberVariables.erase(constVariable);
}
}
auto& variableNamesInStart = ModStaticVariablesNames(startNode);
for (auto& staticVariable : m_staticVariableNames)
{
if (AZStd::find(variableNamesInStart.begin(), variableNamesInStart.end(), staticVariable) == variableNamesInStart.end())
{
variableNamesInStart.push_back(staticVariable);
}
}
}
void AbstractCodeModel::CreateUserFunctionDefinition(const Node& node, const Slot& entrySlot)
{
auto nodeling = azrtti_cast<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>(&node);
auto displayName = nodeling->GetDisplayName();
if (m_uniqueInNames.contains(displayName))
{
AddError
( nodeling->GetEntityId()
, nullptr
, AZStd::string::format
( "%s is the name of multiple In Nodelings in a subgraph,\n"
"this will result in a difficult or impossible to use Function Node when used in another graph", displayName.data()));
return;
}
else
{
m_uniqueInNames.insert(displayName);
}
ExecutionTreePtr definition = OpenScope(nullptr, nodeling, &entrySlot);
definition->SetSymbol(Symbol::FunctionDefinition);
definition->SetName(nodeling->GetDisplayName());
m_userInsThatRequireTopology.insert({ nodeling, definition });
}
bool AbstractCodeModel::CheckCreateUserFunctionDefinition(const Node& node)
{
if (auto nodeling = azrtti_cast<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>(&node))
{
if (auto entrySlot = nodeling->GetEntrySlot())
{
CreateUserFunctionDefinition(node, *entrySlot);
return true;
}
else if (auto exitSlot = nodeling->GetExitSlot())
{
auto displayName = nodeling->GetDisplayName();
if (m_uniqueOutNames.contains(displayName))
{
AddError(nodeling->GetEntityId()
, nullptr
, AZStd::string::format
( "%s is the name of multiple In Nodelings in a subgraph,\n"
"this will result in a difficult or impossible to use Function Node when used in another graph", displayName.data()));
}
else
{
m_uniqueOutNames.insert(displayName);
}
// turn this into a latent in the function node
m_userOutsThatRequireTopology.insert({ nodeling, nullptr });
}
else
{
AddError(nodeling->GetEntityId(), nullptr, ScriptCanvas::ParseErrors::FunctionDefinitionNodeDidNotReturnSlot);
}
}
return false;
}
bool AbstractCodeModel::CreateVariableWriteHandling(const Node& node)
{
if (node.IsVariableWriteHandler())
{
auto addressSlot = node.GetEBusConnectAddressSlot();
AZ_Assert(addressSlot, "variable write handling must have address slot");
AZ_Assert(m_variableWriteHandlingBySlot.find(addressSlot) == m_variableWriteHandlingBySlot.end(), "bad accounting of variable write handling, node has already been parsed");
AZ_Assert(node.IsEBusAddressed(), "variable write handling bus has no address");
auto boundVariableId = node.GetHandlerStartAddress();
if (boundVariableId && boundVariableId->GetAs<GraphScopedVariableId>())
{
if (auto boundVariable = FindBoundVariable(*boundVariableId->GetAs<GraphScopedVariableId>()))
{
AZ_Assert(boundVariable, "variable write handling gave no bound variable");
CreateVariableWriteHandling(*addressSlot, boundVariable, node.IsAutoConnected());
return true;
}
}
}
return false;
}
void AbstractCodeModel::CreateVariableWriteHandling(const Slot& slot, VariableConstPtr boundVariable, bool startsConnected)
{
VariableWriteHandlingPtr variableWriteHandler = AZStd::make_shared<VariableWriteHandling>();
variableWriteHandler->m_variable = boundVariable;
variableWriteHandler->m_startsConnected = startsConnected;
variableWriteHandler->m_isEverConnected = variableWriteHandler->m_startsConnected;
// add to by slot records
m_variableWriteHandlingBySlot.insert({ &slot, variableWriteHandler });
// add to by variable records
auto iter = m_variableWriteHandlingByVariable.find(boundVariable);
if (iter == m_variableWriteHandlingByVariable.end())
{
VariableWriteHandlingSet variableWriteHandlingSet;
variableWriteHandlingSet.insert(variableWriteHandler);
m_variableWriteHandlingByVariable.insert({ boundVariable, variableWriteHandlingSet });
}
else
{
iter->second.insert(variableWriteHandler);
}
}
void AbstractCodeModel::CullUnusedVariables()
{
AZStd::erase_if(m_variables, [this](auto variable)
{
if (IsManuallyDeclaredUserVariable(variable))
{
if (variable->m_isMember)
{
return !this->m_variableUse.memberVariables.contains(variable);
}
else
{
return !this->m_variableUse.localVariables.contains(variable);
}
}
else
{
return false;
}
});
}
bool AbstractCodeModel::ExecutionContainsCyclesCheck(const Node& node, const Slot& outSlot)
{
if (ExecutionContainsCycles(node, outSlot))
{
AddError(nullptr, aznew Internal::ParseError(node.GetEntityId(), AZStd::string::format
( "Execution cycle detected (see connections to %s-%s. Use a looping node like While or For"
, node.GetDebugName().data(), outSlot.GetName().data()).data()));
return true;
}
else
{
return false;
}
}
AbstractCodeModel::ReturnValueConnections AbstractCodeModel::FindAssignments(ExecutionTreeConstPtr execution, const Slot& output)
{
ReturnValueConnections connections;
if (auto variable = FindReferencedVariableChecked(execution, output))
{
connections.m_returnValuesOrReferences.push_back(variable);
}
auto connectedNodes = execution->GetId().m_node->GetConnectedNodes(output);
bool isAtLeastOneReturnValueFound = false;
for (const auto& nodeAndSlot : connectedNodes)
{
auto executionAndReturnVar = FindReturnValueOnThread(execution, nodeAndSlot.first, nodeAndSlot.second);
if (executionAndReturnVar.first)
{
connections.m_returnValuesOrReferences.push_back(executionAndReturnVar.second);
isAtLeastOneReturnValueFound = true;
}
if (IsAutoConnectedLocalEBusHandler(nodeAndSlot.first) || nodeAndSlot.first->IsNodeableNode())
{
auto dataSlots = nodeAndSlot.first->GetOnVariableHandlingDataSlots();
if (AZStd::find(dataSlots.begin(), dataSlots.end(), nodeAndSlot.second) != dataSlots.end())
{
auto iter = m_variableWriteHandlingBySlot.find(nodeAndSlot.second);
if (iter == m_variableWriteHandlingBySlot.end())
{
AddError(nodeAndSlot.first->GetEntityId(), execution, ScriptCanvas::ParseErrors::VariableHandlingMissing);
break;
}
connections.m_returnValuesOrReferences.push_back(iter->second->m_variable);
}
}
}
// if all output is on the thread, and other connections are required, store output, too
// if there are return values off the thread, add an error
connections.m_hasOtherConnections = isAtLeastOneReturnValueFound ? (connectedNodes.size() - 1 > 0) : (!connectedNodes.empty());
return connections;
}
VariableConstPtr AbstractCodeModel::FindBoundVariable(GraphScopedVariableId variableId) const
{
for (auto variable : m_variables)
{
if (variable->m_sourceVariableId == variableId.m_identifier)
{
return variable;
}
}
return nullptr;
}
AbstractCodeModel::ConnectionsInPreviouslyExecutedScope AbstractCodeModel::FindConnectedInputInPreviouslyExecutedScope(ExecutionTreePtr executionWithInput, const EndpointsResolved& scriptCanvasNodesConnectedToInput, FirstNode firstNode) const
{
ConnectionsInPreviouslyExecutedScope result;
ExecutionTreePtr outputChild = firstNode == FirstNode::Self ? nullptr : executionWithInput;
ExecutionTreePtr outputSource = firstNode == FirstNode::Self ? executionWithInput : executionWithInput->ModParent();
while (outputSource)
{
if (IsLooping(outputSource->GetSymbol()))
{
// search loop body, root -> leaves, recursively for output
if (FindConnectedInputInPreviouslyExecutedScopeRecurse(result, outputSource->GetChild(0).m_execution, executionWithInput, scriptCanvasNodesConnectedToInput))
{
result.m_mostParent = outputSource;
}
}
else if (outputSource->GetSymbol() == Symbol::Sequence)
{
size_t childSequenceIndex = outputSource->FindChildIndex(outputChild);
if (childSequenceIndex > 0 && childSequenceIndex < outputSource->GetChildrenCount())
{
do
{
// don't search the child that just missed input
--childSequenceIndex;
// search previous children, root -> leaves, recursively for output
if (FindConnectedInputInPreviouslyExecutedScopeRecurse(result, outputSource->GetChild(childSequenceIndex).m_execution, executionWithInput, scriptCanvasNodesConnectedToInput))
{
result.m_mostParent = outputSource;
}
}
while (childSequenceIndex);
}
}
outputChild = outputSource;
outputSource = outputSource->ModParent();
}
return result;
}
bool AbstractCodeModel::FindConnectedInputInPreviouslyExecutedScopeRecurse(ConnectionsInPreviouslyExecutedScope& result, ExecutionTreeConstPtr outputSource, ExecutionTreePtr executionWithInput, const EndpointsResolved& scriptCanvasNodesConnectedToInput) const
{
size_t originalSize = result.m_connections.size();
auto isConnectedToInput = [&](const Slot* slot)-> EndpointResolved
{
auto iter = AZStd::find_if(scriptCanvasNodesConnectedToInput.begin(), scriptCanvasNodesConnectedToInput.end(),
[&](const EndpointResolved& candidate)
{
return slot && candidate.second == slot;
});
return iter != scriptCanvasNodesConnectedToInput.end() ? *iter : EndpointResolved{nullptr, nullptr};
};
for (size_t childIndex = 0; childIndex < outputSource->GetChildrenCount(); ++childIndex)
{
const ExecutionChild& child = outputSource->GetChild(childIndex);
for (size_t outputIndex = 0; outputIndex < child.m_output.size(); ++outputIndex)
{
const auto& output = child.m_output[outputIndex];
EndpointResolved nodeAndSlot = isConnectedToInput(output.first);
if (nodeAndSlot.second)
{
ConnectionInPreviouslyExecutedScope connection;
connection.m_childIndex = childIndex;
connection.m_outputIndex = outputIndex;
connection.m_source = outputSource;
result.m_connections.push_back(connection);
}
}
if (child.m_execution)
{
FindConnectedInputInPreviouslyExecutedScopeRecurse(result, child.m_execution, executionWithInput, scriptCanvasNodesConnectedToInput);
}
}
return result.m_connections.size() > originalSize;
}
VariableConstPtr AbstractCodeModel::FindConnectedInputInScope(ExecutionTreePtr executionWithInput, const EndpointsResolved& scriptCanvasNodesConnectedToInput, FirstNode firstNode) const
{
ExecutionTreeConstPtr outputChild = executionWithInput;
ExecutionTreeConstPtr outputSource = firstNode == FirstNode::Self ? outputChild : outputChild->GetParent();
while (outputSource)
{
// check every connected SC Node
for (const auto& scNodeAndOutputSlot : scriptCanvasNodesConnectedToInput)
{
const auto outputSCNode = scNodeAndOutputSlot.first;
const auto outputSlot = scNodeAndOutputSlot.second;
const auto mostRecentOutputNodeOnThread = outputSource->GetId().m_node;
if (outputSCNode == mostRecentOutputNodeOnThread)
{
if (!IsPropertyExtractionSlot(outputSource, outputSlot) && (IsVariableGet(outputSource) || IsVariableSet(outputSource)))
{
return outputSource->GetChild(0).m_output[0].second->m_source;
}
for (size_t index(0); index < outputSource->GetChildrenCount(); ++index)
{
auto& child = outputSource->GetChild(index);
if (firstNode == FirstNode::Self || child.m_execution == outputChild)
{
for (const auto& sourceOutputVarPair : child.m_output)
{
// this check fails get/set nodes if not the property extraction slot
if (outputSlot->GetId() == sourceOutputVarPair.second->m_source->m_sourceSlotId)
{
for (const auto& otherConnections : scriptCanvasNodesConnectedToInput)
{
if (otherConnections.first == outputSCNode
&& otherConnections.second != outputSlot
&& InSimultaneousDataPath(*outputSCNode, *otherConnections.second, *outputSlot))
{
AddError(executionWithInput->GetId().m_node->GetEntityId(), executionWithInput, ParseErrors::MultipleSimulaneousInputValues);
}
}
return sourceOutputVarPair.second->m_source;
}
}
}
}
}
}
// look farther up the execution tree
firstNode = FirstNode::Parent;
outputChild = outputSource;
outputSource = outputSource->GetParent();
}
return nullptr;
}
VariableConstPtr AbstractCodeModel::FindVariable(const AZ::EntityId& sourceNodeId) const
{
auto resultIter = AZStd::find_if
( m_variables.begin()
, m_variables.end()
, [&sourceNodeId](const VariableConstPtr& candidate) { return candidate->m_nodeableNodeId == sourceNodeId; });
return resultIter != m_variables.end() ? *resultIter : nullptr;
}
VariableConstPtr AbstractCodeModel::FindVariable(const VariableId& sourceVariableId) const
{
auto resultIter = AZStd::find_if
( m_variables.begin()
, m_variables.end()
, [&sourceVariableId](const VariableConstPtr& candidate) { return candidate->m_sourceVariableId == sourceVariableId; });
return resultIter != m_variables.end() ? *resultIter : nullptr;
}
AZStd::optional<AZStd::string> AbstractCodeModel::FindNodeableSimpleName(VariableConstPtr variable) const
{
if (IsUserNodeable(variable))
{
auto iter = AZStd::find_if(m_nodeablesByNode.begin(), m_nodeablesByNode.end(), [&variable](auto& candidate){ return candidate.second->m_nodeable == variable; });
if (iter != m_nodeablesByNode.end())
{
return iter->second->m_simpleName;
}
}
return {};
}
const AZStd::pair<VariableConstPtr, AZStd::string>* AbstractCodeModel::FindStaticVariable(VariableConstPtr variable) const
{
auto iter = AZStd::find_if
( m_staticVariableNames.begin()
, m_staticVariableNames.end()
, [&](const auto& candidate) { return candidate.first == variable; });
return (iter != m_staticVariableNames.end()) ? iter : nullptr;
}
VariableConstPtr AbstractCodeModel::FindReferencedVariableChecked(ExecutionTreeConstPtr execution, const Slot& slot) const
{
if (slot.IsVariableReference())
{
if (auto variable = FindVariable(slot.GetVariableReference()))
{
return variable;
}
else
{
const_cast<AbstractCodeModel*>(this)->AddError(execution, aznew ParseError(slot.GetNodeId(), AZStd::string::format
( "Failed to find member variable for Variable Reference in slot: %s Id: %s"
, slot.GetName().data()
, slot.GetVariableReference().ToString().data())));
}
}
return nullptr;
}
AZStd::pair<ExecutionTreeConstPtr, VariableConstPtr> AbstractCodeModel::FindReturnValueOnThread(ExecutionTreeConstPtr executionNode, const Node* node, const Slot* slot) const
{
auto root = executionNode->GetRoot();
if (root && IsUserFunctionDefinition(root))
{
for (size_t index(0); index < root->GetReturnValueCount(); ++index)
{
auto returnValue = root->GetReturnValue(index);
if (returnValue.second->m_source->m_sourceSlotId == slot->GetId())
{
// #functions2 slot<->variable determine if the root or the function call should be passed in here...the slot/node lead to the user call on the thread, but it may not even be created yet
return AZStd::make_pair(root, returnValue.second->m_source);
}
}
}
else
{
auto execution = executionNode;
while (execution)
{
if (execution->GetId().m_node == node)
{
for (size_t index(0); index < execution->GetReturnValueCount(); ++index)
{
auto returnValue = execution->GetReturnValue(index);
if (returnValue.second->m_source->m_sourceSlotId == slot->GetId())
{
return AZStd::make_pair(execution, returnValue.second->m_source);
}
}
}
execution = execution->GetParent();
}
}
return {};
}
AZStd::vector<VariablePtr> AbstractCodeModel::FindUserImmediateInput(ExecutionTreePtr call) const
{
AZStd::vector<VariablePtr> inputsVariables;
const auto inputs = call->GetId().m_node->GetSlotsByType(CombinedSlotType::DataOut);
for (const auto& input : inputs)
{
auto iter = m_inputVariableByNodelingInSlot.find(input);
if (iter != m_inputVariableByNodelingInSlot.end())
{
VariablePtr variable = iter->second;
const Slot* slot = iter->first;
variable->m_name = call->ModScope()->AddVariableName(slot->GetName());
variable->m_source = call;
inputsVariables.push_back(variable);
}
}
return inputsVariables;
}
const AbstractCodeModel::ReturnValueDescription* AbstractCodeModel::FindUserImmediateOutput(ExecutionTreePtr call) const
{
auto iter = m_returnValuesByUserFunctionDefinition.find(azrtti_cast<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>(call->GetId().m_node));
return iter != m_returnValuesByUserFunctionDefinition.end() ? &iter->second : nullptr;
}
AZStd::vector<VariablePtr> AbstractCodeModel::FindUserLatentOutput(ExecutionTreePtr call) const
{
AZStd::vector<VariablePtr> outputVariables;
const auto outputs = call->GetId().m_node->GetSlotsByType(CombinedSlotType::DataIn);
for (const auto& output : outputs)
{
auto iter = m_outputVariableByNodelingOutSlot.find(output);
if (iter != m_outputVariableByNodelingOutSlot.end())
{
VariablePtr variable = iter->second;
const Slot* slot = iter->first;
variable->m_name = call->ModScope()->AddVariableName(slot->GetName());
variable->m_source = call;
outputVariables.push_back(variable);
}
}
return outputVariables;
}
AZStd::vector<VariablePtr> AbstractCodeModel::FindUserLatentReturnValues(ExecutionTreePtr call) const
{
AZStd::vector<VariablePtr> returnValues;
const auto outputs = call->GetId().m_node->GetSlotsByType(CombinedSlotType::DataOut);
for (const auto& output : outputs)
{
auto iter = m_returnVariableByNodelingOutSlot.find(output);
if (iter != m_returnVariableByNodelingOutSlot.end())
{
VariablePtr variable = iter->second;
const Slot* slot = iter->first;
variable->m_name = call->ModScope()->AddVariableName(slot->GetName());
variable->m_source = call;
returnValues.push_back(iter->second);
}
}
return returnValues;
}
AZStd::vector<ExecutionTreeConstPtr> AbstractCodeModel::GetAllExecutionRoots() const
{
AZStd::vector<ExecutionTreePtr> nonConstRoots = const_cast<AbstractCodeModel*>(this)->ModAllExecutionRoots();
AZStd::vector<ExecutionTreeConstPtr> constRoots;
constRoots.reserve(nonConstRoots.size());
for (auto root : nonConstRoots)
{
constRoots.push_back(root);
}
return constRoots;
}
AZStd::vector<AZStd::pair<VariableConstPtr, AZStd::string>> AbstractCodeModel::GetAllDeactivationVariables() const
{
AZStd::vector<AZStd::pair<VariableConstPtr, AZStd::string>> variables;
for (const auto& nodeable : m_nodeablesByNode)
{
variables.push_back(AZStd::make_pair(nodeable.second->m_nodeable, k_DeactivateName));
}
for (const auto& ebusHandler : m_ebusHandlingByNode)
{
auto variable = AZStd::make_shared<Variable>();
variable->m_isMember = true;
variable->m_name = ebusHandler.second->m_handlerName;
variable->m_datum = Datum(ebusHandler.second->m_handlerName);
variables.push_back(AZStd::make_pair(variable, k_DeactivateName));
}
for (const auto& eventHandler : m_eventHandlingByNode)
{
variables.push_back(AZStd::make_pair(eventHandler.second->m_handler, k_AzEventHandlerDisconnectName));
}
return variables;
}
const size_t* AbstractCodeModel::GetDebugInfoInIndex(ExecutionTreeConstPtr execution) const
{
auto iter = m_debugMapReverse.m_in.find(execution);
return iter != m_debugMapReverse.m_in.end() ? &iter->second : nullptr;
}
const size_t* AbstractCodeModel::GetDebugInfoOutIndex(ExecutionTreeConstPtr execution, size_t index) const
{
auto iter = m_debugMapReverse.m_out.find(execution);
return iter != m_debugMapReverse.m_out.end() ? &iter->second[index] : nullptr;
}
const size_t* AbstractCodeModel::GetDebugInfoReturnIndex(ExecutionTreeConstPtr execution) const
{
auto iter = m_debugMapReverse.m_return.find(execution);
return iter != m_debugMapReverse.m_return.end() ? &iter->second : nullptr;
}
const size_t* AbstractCodeModel::GetDebugInfoVariableAssignmentIndex(OutputAssignmentConstPtr output, size_t assignmentIndex) const
{
auto outputIter = m_debugMapReverse.m_assignments.find(output);
if (outputIter != m_debugMapReverse.m_assignments.end())
{
auto indexIter = outputIter->second.find(assignmentIndex);
if (indexIter != outputIter->second.end())
{
return &indexIter->second;
}
}
return nullptr;
}
const size_t* AbstractCodeModel::GetDebugInfoVariableSetIndex(OutputAssignmentConstPtr output) const
{
auto iter = m_debugMapReverse.m_variableSets.find(output);
return iter != m_debugMapReverse.m_variableSets.end() ? &iter->second : nullptr;
}
const DebugSymbolMap& AbstractCodeModel::GetDebugMap() const
{
return m_debugMap;
}
const OrderedDependencies& AbstractCodeModel::GetOrderedDependencies() const
{
return m_orderedDependencies;
}
EBusHandlingConstPtr AbstractCodeModel::GetEBusEventHandling(const Node* node) const
{
auto iter = m_ebusHandlingByNode.find(node);
return iter != m_ebusHandlingByNode.end() ? iter->second : nullptr;
}
AZStd::vector<EBusHandlingConstPtr> AbstractCodeModel::GetEBusHandlings() const
{
AZStd::vector<EBusHandlingConstPtr> handlings;
for (auto handling : m_ebusHandlingByNode)
{
handlings.push_back(handling.second);
}
return handlings;
}
EventHandlingConstPtr AbstractCodeModel::GetEventHandling(const Node* node) const
{
auto iter = m_eventHandlingByNode.find(node);
return iter != m_eventHandlingByNode.end() ? iter->second : nullptr;
}
AZStd::vector<EventHandlingConstPtr> AbstractCodeModel::GetEventHandlings() const
{
AZStd::vector<EventHandlingConstPtr> handlings;
for (auto handling : m_eventHandlingByNode)
{
handlings.push_back(handling.second);
}
return handlings;
}
AZStd::vector<ExecutionTreeConstPtr> AbstractCodeModel::GetFunctions() const
{
AZStd::vector<ExecutionTreeConstPtr> functions = m_functions;
for (auto variableHandling : m_variableWriteHandlingBySlot)
{
functions.push_back(variableHandling.second->m_function);
}
return functions;
}
VariableConstPtr AbstractCodeModel::GetImplicitVariable(ExecutionTreeConstPtr execution) const
{
auto iter = m_implicitVariablesByNode.find(execution);
return iter != m_implicitVariablesByNode.end() ? iter->second : nullptr;
}
const SubgraphInterface& AbstractCodeModel::GetInterface() const
{
return m_subgraphInterface;
}
const AZStd::unordered_set<VariableConstPtr>* AbstractCodeModel::GetLocalVariables(ExecutionTreeConstPtr execution) const
{
auto iter = m_variableUseByExecution.find(execution);
return iter != m_variableUseByExecution.end() ? &iter->second.localVariables : nullptr;
}
AZStd::vector<NodeableParseConstPtr> AbstractCodeModel::GetNodeableParse() const
{
AZStd::vector<NodeableParseConstPtr> nodeableParse;
for (auto iter : m_nodeablesByNode)
{
nodeableParse.push_back(iter.second);
}
return nodeableParse;
}
AZStd::string AbstractCodeModel::GetOriginalVariableName(VariableConstPtr variable, const Node* node)
{
if (variable->m_sourceVariableId.IsValid())
{
auto graphVariable = m_source.m_variableData->FindVariable(variable->m_sourceVariableId);
if (graphVariable)
{
return graphVariable->GetVariableName();
}
else
{
AddError(AZ::EntityId(), nullptr, AZStd::string::format("Missing graph variable by source variable id: %s", variable->m_sourceVariableId.ToString().data()).c_str());
}
}
else if (node && variable->m_sourceSlotId.IsValid())
{
if (auto slot = node->GetSlot(variable->m_sourceSlotId))
{
return slot->GetName();
}
else
{
AddError(AZ::EntityId(), nullptr, AZStd::string::format("Missing graph variable by source slot id: %s", variable->m_sourceSlotId.ToString().data()).c_str());
}
}
else
{
AddError(AZ::EntityId(), nullptr, AZStd::string::format("Can't find original slot/variable name for parsed variable"));
}
return "";
}
AZStd::string AbstractCodeModel::GetOutputSlotNameOverride(ExecutionTreePtr execution, const Slot& outputSlot)
{
AZStd::string name;
if (IsPropertyExtractionSlot(execution, &outputSlot))
{
name = outputSlot.GetName();
}
if (IsVariableSet(execution))
{
return GetWrittenVariable(execution)->m_name + name;
}
else if (IsVariableGet(execution))
{
return GetReadVariable(execution) ? GetReadVariable(execution)->m_name + name : "UNKNOWN";
}
return {};
}
AZStd::sys_time_t AbstractCodeModel::GetParseDuration() const
{
return m_parseDuration;
}
ExecutionCharacteristics AbstractCodeModel::GetExecutionCharacteristics() const
{
return m_subgraphInterface.GetExecutionCharacteristics();
}
VariableConstPtr AbstractCodeModel::GetReadVariable(ExecutionTreePtr execution)
{
VariableId variableId = execution->GetId().m_node->GetVariableIdRead(execution->GetId().m_slot);
return variableId.IsValid() ? FindVariable(variableId) : nullptr;
}
const ParsedRuntimeInputs& AbstractCodeModel::GetRuntimeInputs() const
{
return m_runtimeInputs;
}
const Source& AbstractCodeModel::GetSource() const
{
return m_source;
}
const AZStd::string& AbstractCodeModel::GetSourceString() const
{
return m_source.m_assetIdString;
}
const AZStd::vector<AZStd::pair<VariableConstPtr, AZStd::string>>& AbstractCodeModel::GetStaticVariablesNames() const
{
return m_staticVariableNames;
}
const AZStd::vector<AZStd::pair<VariableConstPtr, AZStd::string>>& AbstractCodeModel::GetStaticVariablesNames(ExecutionTreeConstPtr functionBlock) const
{
return const_cast<AbstractCodeModel*>(this)->ModStaticVariablesNames(functionBlock);
}
ExecutionTreeConstPtr AbstractCodeModel::GetStart() const
{
return m_start;
}
VariableConstPtr AbstractCodeModel::GetWrittenVariable(ExecutionTreePtr execution)
{
VariableId variableId = execution->GetId().m_node->GetVariableIdWritten(execution->GetId().m_slot);
return variableId.IsValid() ? FindVariable(variableId) : nullptr;
}
VariableWriteHandlingConstPtr AbstractCodeModel::GetVariableHandling(const Slot* slot) const
{
auto iter = m_variableWriteHandlingBySlot.find(slot);
return iter != m_variableWriteHandlingBySlot.end() ? iter->second : nullptr;
}
VariableWriteHandlingConstSet AbstractCodeModel::GetVariableHandling(VariableConstPtr variable) const
{
VariableWriteHandlingConstSet constSet;
auto byVarIter = m_variableWriteHandlingByVariable.find(variable);
if (byVarIter != m_variableWriteHandlingByVariable.end())
{
for (auto iter : byVarIter->second)
{
constSet.insert(iter);
}
}
return constSet;
}
const AZStd::vector<VariableConstPtr>& AbstractCodeModel::GetVariables() const
{
return m_variables;
}
bool AbstractCodeModel::IsActiveGraph() const
{
if (!m_nodeablesByNode.empty())
{
return true;
}
if (m_subgraphInterface.IsActiveDefaultObject())
{
return true;
}
if (m_subgraphInterface.HasPublicFunctionality())
{
return true;
}
return false;
}
bool AbstractCodeModel::IsAutoConnectedLocalEBusHandler(const Node* node) const
{
auto ebusHandlingIter = m_ebusHandlingByNode.find(node);
return ebusHandlingIter != m_ebusHandlingByNode.end()
&& ebusHandlingIter->second->m_isAutoConnected;
}
bool AbstractCodeModel::IsErrorFree() const
{
return m_isErrorFree;
}
bool AbstractCodeModel::InSimultaneousDataPath(const Node& node, const Slot& reference, const Slot& candidate) const
{
AZStd::vector<const Slot*> combinedOutSlots = node.GetSlotsByType(CombinedSlotType::ExecutionOut);
AZStd::vector<const Slot*> latentSlots = node.GetSlotsByType(CombinedSlotType::LatentOut);
combinedOutSlots.insert(combinedOutSlots.end(), latentSlots.begin(), latentSlots.end());
for (auto outSlot : combinedOutSlots)
{
ConstSlotsOutcome outcome = node.GetSlotsInExecutionThreadByType(*outSlot, CombinedSlotType::DataOut);
if (outcome.IsSuccess())
{
const AZStd::vector<const Slot*>& slots = outcome.GetValue();
if (AZStd::find(slots.begin(), slots.end(), &reference) != slots.end()
&& AZStd::find(slots.begin(), slots.end(), &candidate) != slots.end())
{
return true;
}
}
else
{
AddError(node.GetEntityId(), nullptr, outcome.GetError().data());
}
}
return false;
}
bool AbstractCodeModel::IsPerEntityDataRequired() const
{
return !IsPureLibrary();
}
bool AbstractCodeModel::IsPureLibrary() const
{
return m_subgraphInterface.IsMarkedPure();
}
bool AbstractCodeModel::IsSourceInScope(VariableConstPtr variable, VariableFlags::Scope scope) const
{
auto& sourceVariables = m_source.m_variableData->GetVariables();
if (variable->m_sourceVariableId.IsValid())
{
auto sourceVariableIter = sourceVariables.find(variable->m_sourceVariableId);
if (sourceVariableIter != sourceVariables.end())
{
if (sourceVariableIter->second.IsInScope(scope))
{
return true;
}
}
else
{
AZ_Assert(false, "bad variable id");
}
}
return false;
}
bool AbstractCodeModel::IsUserNodeable() const
{
// #functions2 check the subgraph interface for IsUserNodeable vs User variable
return !IsPureLibrary();
}
bool AbstractCodeModel::IsUserNodeable(VariableConstPtr variable) const
{
return m_userNodeables.contains(variable);
}
void AbstractCodeModel::MarkParseStart()
{
m_parseStartTime = AZStd::chrono::system_clock::now();
}
void AbstractCodeModel::MarkParseStop()
{
m_parseDuration = AZStd::chrono::microseconds(AZStd::chrono::system_clock::now() - m_parseStartTime).count();
}
AZStd::vector<ExecutionTreePtr> AbstractCodeModel::ModAllExecutionRoots()
{
AZStd::vector<ExecutionTreePtr> roots;
if (m_start)
{
roots.push_back(m_start);
}
for (auto& nodeableParse : m_nodeablesByNode)
{
for (auto& latent : nodeableParse.second->m_latents)
{
roots.push_back(AZStd::const_pointer_cast<ExecutionTree>(latent.second));
}
}
for (auto& eventHandlerParse : m_ebusHandlingByNode)
{
for (auto& event : eventHandlerParse.second->m_events)
{
roots.push_back(AZStd::const_pointer_cast<ExecutionTree>(event.second));
}
}
for (auto& eventHandlerParse : m_eventHandlingByNode)
{
roots.push_back(AZStd::const_pointer_cast<ExecutionTree>(eventHandlerParse.second->m_eventHandlerFunction));
}
for (auto variableWriteHandling : m_variableWriteHandlingBySlot)
{
roots.push_back(AZStd::const_pointer_cast<ExecutionTree>(variableWriteHandling.second->m_function));
}
for (auto function : m_functions)
{
roots.push_back(AZStd::const_pointer_cast<ExecutionTree>(function));
}
return roots;
}
AZStd::vector<AZStd::pair<VariableConstPtr, AZStd::string>>& AbstractCodeModel::ModStaticVariablesNames()
{
return m_staticVariableNames;
}
AZStd::vector<AZStd::pair<VariableConstPtr, AZStd::string>>& AbstractCodeModel::ModStaticVariablesNames(Grammar::ExecutionTreeConstPtr functionBlock)
{
auto iter = m_staticVariableNamesByFunctionBlock.find(functionBlock);
if (iter == m_staticVariableNamesByFunctionBlock.end())
{
iter = m_staticVariableNamesByFunctionBlock.insert(AZStd::make_pair(functionBlock, AZStd::vector<AZStd::pair<VariableConstPtr, AZStd::string>>())).first;
}
return iter->second;
}
ExecutionTreePtr AbstractCodeModel::OpenScope(ExecutionTreePtr parent, const Node* node, const Slot* outSlot) const
{
ExecutionTreePtr child = CreateChild(parent, node, outSlot);
child->SetScope(AZStd::make_shared<Scope>());
child->ModScope()->m_parent = parent ? parent->ModScope(): m_graphScope;
return child;
}
void AbstractCodeModel::Parse()
{
MarkParseStart();
m_subgraphInterface.SetNamespacePath(m_source.m_namespacePath);
#if defined(FUNCTION_LEGACY_SUPPORT_ENABLED)
if (m_source.m_graph->IsFunctionGraph())
{
m_variableScopeMeaning = VariableScopeMeaning_LegacyFunctions::FunctionPrototype;
m_subgraphInterface.MarkAllInputOutputShared();
}
else
{
m_variableScopeMeaning = VariableScopeMeaning_LegacyFunctions::ValueInitialization;
}
#endif
AddAllVariablesPreParse();
if (!IsErrorFree())
{
return;
}
for (auto& nodeEntity : m_source.m_graphData->m_nodes)
{
if (nodeEntity)
{
if (auto node = AZ::EntityUtils::FindFirstDerivedComponent<Node>(nodeEntity))
{
if (Parse(*node))
{
m_possibleExecutionRoots.push_back(node);
}
}
else
{
AddError(nullptr, ValidationConstPtr(aznew NullNodeInGraph(nodeEntity->GetId(), nodeEntity->GetName())));
}
}
else
{
AddError(nullptr, ValidationConstPtr(aznew NullEntityInGraph()));
}
if (!IsErrorFree())
{
return;
}
}
if (!IsErrorFree())
{
return;
}
ParseAutoConnectedEBusHandlerVariables();
Parse(m_startNodes);
for (auto node : m_possibleExecutionRoots)
{
ParseExecutionTreeRoots(*node);
}
ParseVariableHandling();
ParseUserFunctionTopology();
ParseConstructionInputVariables();
ParseExecutionCharacteristics();
// from here on, nothing more needs to happen during simple parsing
// for example, in the editor, to get validation on syntax based effects for the view
// parsing could stop now
if (IsErrorFree())
{
ParseDependenciesAssetIndicies();
ConvertNamesToIdentifiers();
if (m_source.m_addDebugInfo)
{
AddDebugInformation();
}
if (!IsActiveGraph())
{
if (m_source.m_graphData->m_nodes.empty())
{
AddError(AZ::EntityId(), nullptr, ScriptCanvas::ParseErrors::EmptyGraph);
}
else
{
AddError(nullptr, ValidationConstPtr(aznew InactiveGraph()));
}
}
else
{
MarkParseStop();
if (m_source.m_printModelToConsole)
{
AZStd::string pretty;
PrettyPrint(pretty, *this);
AZ_TracePrintf("ScriptCanvas", "%s", pretty.data());
AZ_TracePrintf("ScriptCanvas", "SubgraphInterface:");
AZ_TracePrintf("ScriptCanvas", ToString(m_subgraphInterface).data());
}
AZ_TracePrintf("Script Canvas", "Parse Duration: %8.4f ms\n", m_parseDuration / 1000.0);
}
}
}
void AbstractCodeModel::ParseAutoConnectedEBusHandlerVariables()
{
// *** NOTE *** this means that for all ebus connect calls, the input is broken
// if it is an auto connected ebus, the input can't be the output of the previous node
// it has to be the member variable which will then be written to
// so that will have to get fixed up
for (auto& nodeAndHandling : m_ebusHandlingByNode)
{
EBusHandlingConstPtr ebusHandling = nodeAndHandling.second;
if (ebusHandling->m_isAutoConnected)
{
m_subgraphInterface.MarkActiveDefaultObject();
if (ebusHandling->m_isAddressed)
{
m_subgraphInterface.MarkActiveDefaultObject();
const auto name = m_graphScope->AddFunctionName(AZStd::string::format("On%sAddressChanged", ebusHandling->m_ebusName.c_str()).c_str());
ebusHandling->m_startingAdress->m_isMember = true;
// add a new variable handling, that is never disconnected, at always controls this ebus connection
// make bound variables of their data, if it doesn't exist already
// mark all of these variables as handler control addresses
auto addressSlot = nodeAndHandling.first->GetEBusConnectAddressSlot();
AZ_Assert(addressSlot, "addressed ebus handler node must have address slot.");
CreateVariableWriteHandling(*addressSlot, ebusHandling->m_startingAdress, nodeAndHandling.first->IsAutoConnected());
VariableWriteHandlingPtr addressChangeHandling = AZStd::const_pointer_cast<VariableWriteHandling>(GetVariableHandling(addressSlot));
AZ_Assert(addressChangeHandling != nullptr, "failure to create variable handling for ebus address");
ExecutionTreePtr onAddressChange = OpenScope(nullptr, nodeAndHandling.first, nullptr);
onAddressChange->SetSymbol(Symbol::FunctionDefinition);
onAddressChange->SetName(name);
// add the disconnect call
ExecutionTreePtr disconnect = CreateChild(onAddressChange, nodeAndHandling.first, nodeAndHandling.first->GetEBusDisconnectSlot());
ParseInputThisPointer(disconnect);
onAddressChange->AddChild({ nullptr, {}, disconnect });
// add the connect call
ExecutionTreePtr connect = CreateChild(disconnect, nodeAndHandling.first, nodeAndHandling.first->GetEBusConnectSlot());
ParseInputThisPointer(connect);
connect->AddInput({ nullptr, ebusHandling->m_startingAdress, DebugDataSource::FromInternal() });
disconnect->AddChild({ nullptr, {}, connect });
FunctionCallDefaultMetaData metaData;
metaData.PostParseExecutionTreeBody(*this, connect);
metaData.PostParseExecutionTreeBody(*this, disconnect);
addressChangeHandling->m_function = onAddressChange;
}
}
}
}
void AbstractCodeModel::Parse(const AZStd::vector<const Nodes::Core::Start*>& startNodes)
{
if (startNodes.empty() && m_subgraphStartCalls.empty())
{
return;
}
auto startNode = !startNodes.empty() ? startNodes.front()
: !m_subgraphStartCalls.empty() ? *m_subgraphStartCalls.begin() : nullptr;
ExecutionTreePtr start = OpenScope(nullptr, startNode, nullptr);
start->SetSymbol(Symbol::FunctionDefinition);
if (!m_subgraphStartCalls.empty())
{
m_start = start;
for (auto node : m_subgraphStartCalls)
{
ExecutionTreePtr childStartCall = CreateChild(start, node, nullptr);
childStartCall->SetSymbol(Symbol::FunctionCall);
childStartCall->SetName(k_OnGraphStartFunctionName);
childStartCall->MarkStart();
auto lexicalScopeOutcome = node->GetFunctionCallLexicalScope(nullptr);
if (lexicalScopeOutcome.IsSuccess())
{
childStartCall->SetNameLexicalScope(lexicalScopeOutcome.GetValue());
ParseInputThisPointer(childStartCall);
start->AddChild({ nullptr, {}, childStartCall });
start = childStartCall;
}
else
{
AddError(node->GetEntityId(), nullptr, ScriptCanvas::ParseErrors::SubgraphOnGraphStartFailedToReturnLexicalScope);
return;
}
}
}
EndpointsResolved outNodes;
AZStd::vector<const Slot*> outSlots;
for (auto startNode2 : startNodes)
{
auto& node = *startNode2;
auto outSlot = node.GetSlot(node.GetSlotIdByType("Out", CombinedSlotType::ExecutionOut));
if (!outSlot)
{
AddError(node.GetEntityId(), nullptr, ScriptCanvas::ParseErrors::NoOutSlotInStart);
return;
}
if (ExecutionContainsCyclesCheck(node, *outSlot))
{
return;
}
auto executionOutNodes = node.GetConnectedNodes(*outSlot);
if (!executionOutNodes.empty())
{
outNodes.insert(outNodes.end(), executionOutNodes.begin(), executionOutNodes.end());
outSlots.insert(outSlots.end(), executionOutNodes.size(), outSlot);
}
}
if (!outSlots.empty())
{
start->AddChild({ outSlots[0], {}, nullptr });
start->MarkStart();
ParseExecutionMultipleOutSyntaxSugar(start, outNodes, outSlots);
PostParseProcess(start);
PostParseErrorDetect(start);
if (!IsErrorFree())
{
start->Clear();
if (m_start)
{
m_start->Clear();
}
AddError(AZ::EntityId{}, nullptr, ScriptCanvas::ParseErrors::StartNodeFailedToParse);
return;
}
if (!m_start)
{
m_start = start;
}
}
else
{
// add warning or notification on useless start node?
}
if (m_start)
{
m_start->SetName(k_OnGraphStartFunctionName);
m_subgraphInterface.MarkOnGraphStart();
}
}
bool AbstractCodeModel::Parse(const Node& node)
{
if (!node.IsNodeEnabled())
{
return false;
}
if (auto functionNode = azrtti_cast<const Nodes::Core::FunctionCallNode*>(&node))
{
Nodes::Core::FunctionCallNodeCompareConfig config;
if (functionNode->IsOutOfDate(config))
{
AZ_Warning("ScriptCanvas", false, "%s node is out-of-date.", node.GetNodeName().c_str());
AddError(nullptr, aznew NodeCompatiliblity::NodeOutOfDate(node.GetEntityId(), node.GetNodeName()));
return false;
}
}
else if (node.IsOutOfDate(m_source.m_graph->GetVersion()))
{
AZ_Warning("ScriptCanvas", false, "%s node is out-of-date.", node.GetNodeName().c_str());
AddError(nullptr, aznew NodeCompatiliblity::NodeOutOfDate(node.GetEntityId(), node.GetNodeName()));
return false;
}
if (auto start = azrtti_cast<const Nodes::Core::Start*>(&node))
{
m_startNodes.push_back(start);
return false;
}
else
{
ParseDependencies(node);
ParseImplicitVariables(node);
return CheckCreateRoot(node);
}
}
bool AbstractCodeModel::Parse(VariableWriteHandlingPtr variableHandling)
{
if (!variableHandling->m_startsConnected && !variableHandling->m_isEverConnected)
{
AddError(AZ::EntityId(), variableHandling->m_function, ScriptCanvas::ParseErrors::InfiniteLoopWritingToVariable);
return false;
}
if (IsInfiniteVariableWriteHandlingLoop(*this, variableHandling, variableHandling->m_function, true))
{
AddError(variableHandling->m_function, aznew Internal::ParseError(AZ::EntityId(), ScriptCanvas::ParseErrors::InfiniteLoopWritingToVariable));
return false;
}
if (variableHandling->m_connectionVariable && !variableHandling->RequiresConnectionControl())
{
variableHandling->m_connectionVariable->m_isMember = false;
m_variables.erase(AZStd::remove(m_variables.begin(), m_variables.end(), variableHandling->m_connectionVariable), m_variables.end());
variableHandling->m_connectionVariable = nullptr;
}
return true;
}
AbstractCodeModelConstPtr AbstractCodeModel::Parse(const Source& source, bool terminateOnError, bool terminateOnInternalError)
{
return AZStd::make_shared<AbstractCodeModel>(source, terminateOnError, terminateOnInternalError);
}
void AbstractCodeModel::ParseExecutionBreak(ExecutionTreePtr execution)
{
if (auto forEach = azrtti_cast<const ScriptCanvas::Nodes::Core::ForEach*>(execution->GetId().m_node))
{
if (forEach->GetLoopBreakSlotId() == execution->GetId().m_slot->GetId())
{
auto target = execution->GetParent();
while (target)
{
if (auto forEach2 = azrtti_cast<const ScriptCanvas::Nodes::Core::ForEach*>(target->GetId().m_node))
{
// This check is to make sure ForEach break slot is connected within correct execution scope
// 1. parent execution should be the same foreach node as current execution
// 2. parent execution slot should be loop each slot
if (forEach2 != execution->GetId().m_node || forEach2->GetLoopSlotId() != target->GetId().m_slot->GetId())
{
AddError(forEach2->GetEntityId(), execution, ScriptCanvas::ParseErrors::BreakNotInForEachScope);
}
break;
}
target = target->GetParent();
}
}
}
execution->SetSymbol(Symbol::Break);
}
VariableConstPtr AbstractCodeModel::ParseConnectedInputData(const Slot& inputSlot, ExecutionTreePtr executionWithInput, const EndpointsResolved& scriptCanvasNodesConnectedToInput, FirstNode firstNode)
{
if (auto inScopeVar = FindConnectedInputInScope(executionWithInput, scriptCanvasNodesConnectedToInput, firstNode))
{
return inScopeVar;
}
// do this exact thing for multiple out sequence sugar, and change the way those are translated
auto inPreviouslyExecutedScopeResult = FindConnectedInputInPreviouslyExecutedScope(executionWithInput, scriptCanvasNodesConnectedToInput, firstNode);
if (!inPreviouslyExecutedScopeResult.m_connections.empty())
{
// get the required scope
auto mostForLoopParent = inPreviouslyExecutedScopeResult.m_mostParent->ModParent();
// add a variable to the required scope
auto newInputResultOfAssignment = AZStd::make_shared<Variable>();
auto variableName = mostForLoopParent->ModScope()->AddVariableName(inputSlot.GetName(), "input");
newInputResultOfAssignment->m_name = variableName;
newInputResultOfAssignment->m_datum = Datum(inputSlot.GetDataType(), Datum::eOriginality::Original);
// create a variable declaration
ExecutionTreePtr variableConstruction = CreateChild(inPreviouslyExecutedScopeResult.m_mostParent->ModParent(), nullptr, nullptr);
variableConstruction->AddInput({ nullptr , newInputResultOfAssignment, DebugDataSource::FromInternal()});
variableConstruction->SetSymbol(Symbol::VariableDeclaration);
// splice the variable declaration right before the most parent for loop is executed
auto positionOutcome = mostForLoopParent->RemoveChild(inPreviouslyExecutedScopeResult.m_mostParent);
if (!positionOutcome.IsSuccess())
{
AddError(AZ::EntityId(), executionWithInput, ScriptCanvas::ParseErrors::FailedToRemoveChild);
}
auto position = positionOutcome.TakeValue();
position.second.m_execution = variableConstruction;
mostForLoopParent->InsertChild(position.first, position.second);
variableConstruction->AddChild({ nullptr, {}, inPreviouslyExecutedScopeResult.m_mostParent });
inPreviouslyExecutedScopeResult.m_mostParent->SetParent(variableConstruction);
// add the variable to to the assignments lists of all connections the child had to nodes in parent loop bodies
AddPreviouslyExecutedScopeVariableToOutputAssignments(newInputResultOfAssignment, inPreviouslyExecutedScopeResult);
// finally, return the newly created variable to the input of the child SC node that is directly connected
return newInputResultOfAssignment;
}
// \todo add member variable if data has crossed threads, maybe make it opt-in?
return nullptr;
}
void AbstractCodeModel::ParseConstructionInputVariables()
{
AZStd::vector<AZStd::pair<AZ::EntityId, Nodeable*>> nodeablesById;
AZStd::vector<VariableId> inputVariableIds;
AZStd::unordered_map<VariableId, Grammar::VariableConstPtr> inputVariablesById;
auto& variables = GetVariables();
for (auto variable : variables)
{
auto constructionRequirement = ParseConstructionRequirement(variable);
switch (constructionRequirement)
{
case VariableConstructionRequirement::None:
break;
case VariableConstructionRequirement::InputEntityId:
{
m_runtimeInputs.m_entityIds.emplace_back(variable->m_sourceVariableId, *variable->m_datum.GetAs<Data::EntityIDType>());
}
break;
case VariableConstructionRequirement::InputNodeable:
{
if (variable->m_datum.Empty())
{
AddError(nullptr, aznew Internal::ParseError(AZ::EntityId{}, "Empty nodeable datum in variable, probably due to a problem with azrtti declarations"));
break;
}
// I solemnly swear no harm shall come to the nodeable
const Nodeable* nodeableSource = reinterpret_cast<const Nodeable*>(variable->m_datum.GetAsDanger());
if (!nodeableSource)
{
AddError(nullptr, aznew Internal::ParseError(AZ::EntityId{}, "No raw nodeable held by variable"));
break;
}
nodeablesById.push_back({ variable->m_nodeableNodeId, const_cast<Nodeable*>(nodeableSource) });
}
break;
case VariableConstructionRequirement::InputVariable:
{
auto variableID = variable->m_sourceVariableId.IsValid() ? variable->m_sourceVariableId : VariableId::MakeVariableId();
inputVariableIds.push_back(variableID);
inputVariablesById.insert({ variableID, variable });
// sort revealed a datum copy issue: type is not preserved, workaround below
// m_runtimeInputs.m_variables.emplace_back(variable->m_sourceVariableId, variable->m_datum);
}
break;
case VariableConstructionRequirement::Static:
m_runtimeInputs.m_staticVariables.push_back({ variable->m_sourceVariableId, variable->m_datum.ToAny() });
break;
default:
AZ_Assert(constructionRequirement == VariableConstructionRequirement::None, "new requirement added and not handled");
break;
}
}
AZStd::sort(nodeablesById.begin(), nodeablesById.end(), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; });
m_runtimeInputs.m_nodeables.reserve(nodeablesById.size());
for (auto& idAndNodeable : nodeablesById)
{
m_runtimeInputs.m_nodeables.push_back(idAndNodeable.second);
}
// sort revealed a datum copy issue: type is not preserved, workaround below
// AZStd::sort(m_runtimeInputs.m_variables.begin(), m_runtimeInputs.m_variables.end(), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; });
m_runtimeInputs.m_variables.reserve(inputVariableIds.size());
AZStd::sort(inputVariableIds.begin(), inputVariableIds.end());
for (auto variableId : inputVariableIds)
{
auto iter = inputVariablesById.find(variableId);
AZ_Assert(iter != inputVariablesById.end(), "missing variable id from list just constructed");
m_runtimeInputs.m_variables.emplace_back(iter->first, iter->second->m_datum);
}
AZStd::sort(m_runtimeInputs.m_entityIds.begin(), m_runtimeInputs.m_entityIds.end(), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; });
AZStd::sort(m_runtimeInputs.m_staticVariables.begin(), m_runtimeInputs.m_staticVariables.end(), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; });
auto allStaticVariables = ToVariableList(m_runtimeInputs.m_staticVariables);
auto& staticVariableNames = ModStaticVariablesNames();
for (auto staticVariable : allStaticVariables)
{
AZStd::string name = AddTranslationVariableName(AZStd::string::format("s_%sCloneSource", staticVariable->m_name.c_str()));
staticVariableNames.push_back({ staticVariable, name });
if (!staticVariable->m_isMember && !staticVariable->m_isFromFunctionDefinitionSlot)
{
if (staticVariable->m_source)
{
auto& localStatics = ModStaticVariablesNames(staticVariable->m_source);
auto iter = AZStd::find_if
( localStatics.begin()
, localStatics.end()
, [&](const auto& candidate) { return candidate.first == staticVariable; });
if (iter == localStatics.end())
{
localStatics.push_back({ staticVariable, name });
}
}
}
}
if (!(m_runtimeInputs.m_nodeables.empty() && m_runtimeInputs.m_variables.empty() && m_runtimeInputs.m_entityIds.empty()))
{
m_subgraphInterface.MarkRequiresConstructionParameters();
}
}
ConstSlotsOutcome AbstractCodeModel::ParseDataOutSlots(ExecutionTreePtr execution, ExecutionChild& executionChild) const
{
if (!execution->GetId().m_node)
{
return AZ::Failure(AZStd::string("null node in AbstractCodeModel::ParseDataOutSlots"));
}
if (!execution->GetId().m_slot)
{
return AZ::Failure(AZStd::string("null slot in AbstractCodeModel::ParseDataOutSlots"));
}
auto outcome = execution->GetId().m_node->GetSlotsInExecutionThreadByType(*(execution->GetId().m_slot), CombinedSlotType::DataOut, executionChild.m_slot);
if (!outcome.IsSuccess())
{
return outcome;
}
return AZ::Success(outcome.GetValue());
}
void AbstractCodeModel::ParseDeactivation()
{
AZStd::vector<AZStd::pair<VariableConstPtr, AZStd::string>> deactivatables = GetAllDeactivationVariables();
if (!IsPureLibrary() || !deactivatables.empty())
{
ExecutionTreePtr deactivate = OpenScope(nullptr, nullptr, nullptr);
deactivate->SetSymbol(Symbol::FunctionDefinition);
deactivate->SetName(k_DeactivateName);
if (deactivatables.empty())
{
ExecutionTreePtr empty = CreateChild(deactivate, nullptr, nullptr);
empty->MarkDebugEmptyStatement();
deactivate->AddChild({ nullptr, {}, empty });
}
else
{
auto previous = deactivate;
for (const auto& variable : deactivatables)
{
ExecutionTreePtr deactivate2 = CreateChild(previous, nullptr, nullptr);
deactivate2->SetSymbol(Symbol::FunctionCall);
deactivate2->SetNameLexicalScope(LexicalScope::Variable());
deactivate2->SetName(variable.second);
deactivate2->AddInput({ nullptr, variable.first, DebugDataSource::FromInternal() });
previous->AddChild({ nullptr, {}, deactivate2 });
previous = deactivate2;
}
}
m_functions.push_back(deactivate);
}
}
void AbstractCodeModel::ParseDebugInformation(ExecutionTreePtr execution)
{
switch (execution->GetSymbol())
{
// nothing required
case Symbol::PlaceHolderDuringParsing:
break;
// out and return value information
case Symbol::FunctionDefinition:
AddDebugInformationFunctionDefinition(execution);
break;
// add in out everything
case Symbol::ForEach:
case Symbol::FunctionCall:
case Symbol::OperatorAddition:
case Symbol::OperatorDivision:
case Symbol::OperatorMultiplication:
case Symbol::OperatorSubraction:
case Symbol::RandomSwitch:
case Symbol::Switch:
case Symbol::While:
AddDebugInformationIn(execution);
AddDebugInformationOut(execution);
break;
// add in but not out
case Symbol::Break:
case Symbol::LogicalAND:
case Symbol::LogicalNOT:
case Symbol::LogicalOR:
case Symbol::CompareEqual:
case Symbol::CompareGreater:
case Symbol::CompareGreaterEqual:
case Symbol::CompareLess:
case Symbol::CompareLessEqual:
case Symbol::CompareNotEqual:
AddDebugInformationIn(execution);
break;
// add in-debug-info if the if condition is NOT prefixed with logic or comparison expression (which will have the in-debug-info)
// add out-debug-info in all cases including including empty cases
case Symbol::IfCondition:
if (!(execution->GetId().m_node->IsIfBranchPrefacedWithBooleanExpression()))
{
AddDebugInformationIn(execution);
}
AddDebugInformationOut(execution);
break;
default:
break;
}
}
void AbstractCodeModel::ParseDependencies(const Node& node)
{
const auto dependencyOutcome = node.GetDependencies();
if (dependencyOutcome.IsSuccess())
{
const auto& dependencies = dependencyOutcome.GetValue();
// #functions2 this search needs to recurse, this layer of dependencies will only be one step deep
// currently this problem is found by the asset processor
if (dependencies.userSubgraphs.find(m_source.m_namespacePath) != dependencies.userSubgraphs.end())
{
AZStd::string circularDependency = AZStd::string::format
( ParseErrors::CircularDependencyFormat
, m_source.m_name.data()
, node.GetDebugName().data()
, m_source.m_name.data());
AddError(nullptr, aznew Internal::ParseError(node.GetEntityId(), circularDependency));
}
// this part must NOT recurse, the dependency tree should remain a tree and not be flattened
m_orderedDependencies.source.MergeWith(dependencies);
}
else
{
AddError(nullptr, ValidationConstPtr(aznew DependencyRetrievalFailiure(node.GetEntityId())));
}
if (auto subgraphInterface = node.GetSubgraphInterface())
{
m_subgraphInterface.MergeExecutionCharacteristics(*subgraphInterface);
if (subgraphInterface->HasOnGraphStart())
{
m_subgraphStartCalls.insert(&node);
}
if (subgraphInterface->IsActiveDefaultObject())
{
m_activeDefaultObject.insert(&node);
}
}
}
void AbstractCodeModel::ParseDependenciesAssetIndicies()
{
for (const auto& subgraphAssetID : m_orderedDependencies.source.userSubgraphAssetIds)
{
m_orderedDependencies.orderedAssetIds.push_back(subgraphAssetID);
}
}
void AbstractCodeModel::ParseEntityIdInput(ExecutionTreePtr execution)
{
for (size_t index(0); index < execution->GetInputCount(); ++index)
{
auto& slotAndVariable = execution->GetInput(index);
if (auto& input = slotAndVariable.m_value)
{
if (!input->m_sourceVariableId.IsValid() && IsEntityIdThatRequiresRuntimeRemap(input))
{
input->m_sourceVariableId = VariableId::MakeVariableId();
input->m_source = nullptr;
// promote to member variable for at this stage, optimizations on data flow will occur later
input->m_isMember = true;
input->m_name = m_graphScope->AddVariableName(input->m_name);
AddVariable(input);
}
}
else
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), AZStd::string::format("null input in ParseEntityIdInput %zu", index)));
}
}
for (size_t index(0); index < execution->GetChildrenCount(); ++index)
{
auto& child = execution->ModChild(index);
if (child.m_execution)
{
ParseEntityIdInput(child.m_execution);
}
}
}
void AbstractCodeModel::ParseExecutionCharacteristics()
{
// parse each function, individually, but not the start function
// if all functions are pure, mark the start function pure
bool allRootsArePure = true;
auto roots = ModAllExecutionRoots();
for (auto root : roots)
{
if (root != m_start)
{
ParseExecutionCharacteristics(root);
const bool rootIsPure = root->IsPure();
allRootsArePure = allRootsArePure && rootIsPure;
if (rootIsPure)
{
if (auto functionDefNode = azrtti_cast<const Nodes::Core::FunctionDefinitionNode*>(root->GetId().m_node))
{
if (auto in = m_subgraphInterface.ModIn(functionDefNode->GetIdentifier()))
{
in->isPure = true;
}
else
{
AddError(root->GetId().m_node->GetEntityId(), root, "Accounting error, missing Execution-In in SubgraphInterface.");
}
}
}
}
}
// The start function gets special handling. If all of the rest of the graph can be considered pure, than no matter what,
// the start function will only run once. Therefore all the member variables are local to start (if used at all), and
// the start function itself can be considered pure.
if (m_start)
{
// The start function is only parsed for variable use. This way, if the graph is in all other ways pure, the
// start function can be pure, even if it has nodes like Cycle or Once. The state of those nodes can now be
// considered local state.
ParseVariableUseAndPurity(m_start);
}
CullUnusedVariables();
if (allRootsArePure
&& m_ebusHandlingByNode.empty()
&& m_eventHandlingByNode.empty()
&& m_nodeablesByNode.empty()
&& m_variableWriteHandlingByVariable.empty()
&& m_subgraphInterface.IsParsedPure())
{
if (m_start)
{
m_start->MarkPure();
ConvertAllMemberVariablesToLocal(m_start);
}
m_subgraphInterface.MarkExecutionCharacteristics(ExecutionCharacteristics::Pure);
}
else
{
m_subgraphInterface.MarkExecutionCharacteristics(ExecutionCharacteristics::Object);
}
if (m_subgraphInterface.GetExecutionCharacteristics() == ExecutionCharacteristics::Object)
{
ParseDeactivation();
}
}
void AbstractCodeModel::ParseExecutionCharacteristics(ExecutionTreePtr execution)
{
if (!ParseVariableUseAndPurity(execution))
{
return;
}
if (execution->IsOnLatentPath())
{
return;
}
if (execution->HasExplicitUserOutCalls())
{
return;
}
execution->MarkPure();
}
void AbstractCodeModel::ParseExecutionCycleStatement(ExecutionTreePtr executionCycle)
{
auto cycleVariableIter = m_controlVariablesBySourceNode.find(executionCycle->GetId().m_node);
AZ_Assert(cycleVariableIter != m_controlVariablesBySourceNode.end(), "cycle node didn't add a control variable to graph scope");
executionCycle->AddInput({ nullptr, cycleVariableIter->second, DebugDataSource::FromInternal() });
executionCycle->SetSymbol(Symbol::Cycle);
ParseExecutionSequentialChildren(executionCycle);
}
void AbstractCodeModel::ParseExecutionLoop(ExecutionTreePtr executionLoop)
{
AddDebugInfiniteLoopDetectionInLoop(executionLoop);
auto loopSlot = executionLoop->GetId().m_node->GetSlot(executionLoop->GetId().m_node->GetLoopSlotId());
AZ_Assert(loopSlot, "Node did not return a valid loop slot");
ExecutionTreePtr executionLoopBody = OpenScope(executionLoop, executionLoop->GetId().m_node, loopSlot);
executionLoopBody->SetSymbol(Symbol::PlaceHolderDuringParsing);
executionLoopBody->MarkInputOutputPreprocessed();
executionLoop->AddChild({ loopSlot, {}, executionLoopBody });
auto breakSlot = executionLoop->GetId().m_node->GetSlot(executionLoop->GetId().m_node->GetLoopFinishSlotId());
AZ_Assert(breakSlot, "Node did not return a valid loop break slot");
ExecutionTreePtr executionBreak = CreateChild(executionLoop, executionLoop->GetId().m_node, breakSlot);
executionBreak->SetSymbol(Symbol::PlaceHolderDuringParsing);
executionBreak->MarkInputOutputPreprocessed();
executionLoop->AddChild({ breakSlot, {}, executionBreak });
executionLoopBody = ParseExecutionForEachLoop(executionLoopBody, *loopSlot, *breakSlot);
if (!executionLoopBody)
{
return;
}
ParseExecutionTreeBody(executionLoopBody, *loopSlot);
// \todo check if the loop data is ever connected, and whether it can be known that pure iteration has zero side effects
// otherwise, this optimization cannot be used
// if (executionLoopBody->GetChild(0).m_execution->GetSymbol() == Symbol::PlaceHolderDuringParsing)
// {
// executionLoopBody->SetSymbol(Symbol::PlaceHolderDuringParsing);
// }
ParseExecutionTreeBody(executionBreak, *breakSlot);
if (executionBreak->GetChildrenCount() == 1 && executionBreak->GetChild(0).m_execution->GetSymbol() == Symbol::PlaceHolderDuringParsing)
{
executionBreak->SetSymbol(Symbol::PlaceHolderDuringParsing);
}
}
ExecutionTreePtr AbstractCodeModel::ParseExecutionForEachLoop(ExecutionTreePtr forEachLoopBody, const Slot& loopSlot, const Slot& /*breakSlot*/)
{
auto forEach = forEachLoopBody->ModParent();
if (forEach->GetSymbol() != Symbol::ForEach)
{
return forEachLoopBody;
}
if (forEach->GetInputCount() == 0)
{
AddError(forEach->GetNodeId(), forEach, ScriptCanvas::ParseErrors::NoInputToForEach);
return nullptr;
}
auto forEachNodeSC = azrtti_cast<const Nodes::Core::ForEach*>(forEach->GetId().m_node);
AZ_Assert(forEachNodeSC, "null ForEach ScriptCanvas node in for each loop parse");
ForEachMetaData* forEachMetaData = aznew ForEachMetaData();
forEach->SetMetaData(MetaDataPtr(forEachMetaData));
const auto sourceType = forEach->GetInput(0).m_value->m_datum.GetType();
const char* sourceName = forEach->GetInput(0).m_value->m_name.c_str();
auto scope = forEach->ModScope();
forEachMetaData->m_iteratorVariableName = scope->AddVariableName(sourceName, "iter");
forEachMetaData->m_isNotAtEndFunctionVariableName = scope->AddVariableName(sourceName, "is_not_at_end_func");
forEachMetaData->m_nextFunctionVariableName = scope->AddVariableName(sourceName, "next_func");
forEachMetaData->m_valueFunctionVariableName = scope->AddVariableName(sourceName, "get_value_func");
if (Data::IsMapContainerType(sourceType))
{
forEachMetaData->m_isKeyRequired = true;
forEachMetaData->m_keyFunctionVariableName = scope->AddVariableName(sourceName, "get_key_func");
}
/// NOTE: after basic iteration works correctly
/// \todo when subsequent input (from nodes connected to the break slot) looks up the slot from the node with key or the value,
/// it is going to have to find the output of the get/key value functions in BOTH the child outs of break and loop
/// LY-109862 may be required for this
ExecutionTreePtr lastExecution = forEachLoopBody;
// create the iterator variable
auto iteratorVariable = AZStd::make_shared<Variable>();
iteratorVariable->m_source = forEach;
iteratorVariable->m_name = forEachMetaData->m_iteratorVariableName;
// the type here shouldn't matter
iteratorVariable->m_datum = Datum(Data::Type::Number(), Datum::eOriginality::Original);
if (forEachMetaData->m_isKeyRequired)
{
// add a function call node for the key, use the name, make input output
auto getKey = lastExecution;
getKey->SetName(forEachMetaData->m_keyFunctionVariableName);
getKey->SetSymbol(Symbol::FunctionCall);
getKey->AddInput({ nullptr, iteratorVariable, DebugDataSource::FromInternal() });
getKey->MarkInputOutputPreprocessed();
auto keySlot = forEachNodeSC->GetSlot(forEachNodeSC->GetKeySlotId());
AZ_Assert(keySlot, "no key slot in for each node");
lastExecution = CreateChild(getKey, getKey->GetId().m_node, &loopSlot);
getKey->AddChild({ &loopSlot, {}, lastExecution });
ExecutionChild& getKeyChild = getKey->ModChild(0);
getKeyChild.m_output.push_back({ keySlot, CreateOutputData(getKey, getKeyChild, *keySlot) });
lastExecution->SetSymbol(Symbol::PlaceHolderDuringParsing);
}
// create the get value function call node
auto getValue = lastExecution;
getValue->SetName(forEachMetaData->m_valueFunctionVariableName);
getValue->SetSymbol(Symbol::FunctionCall);
getValue->AddInput({ nullptr, iteratorVariable, DebugDataSource::FromInternal() });
getValue->MarkInputOutputPreprocessed();
auto valueSlot = forEachNodeSC->GetSlot(forEachNodeSC->GetValueSlotId());
AZ_Assert(valueSlot, "no value slot in for each node");
lastExecution->AddChild({});
auto outputValue = CreateOutputData(lastExecution, lastExecution->ModChild(0), *valueSlot);
lastExecution->ModChild(0).m_output.push_back({ valueSlot, outputValue });
// the former place holder is now a function call to retrieve values from the container
return lastExecution;
}
void AbstractCodeModel::ParseExecutionForEachStatement(ExecutionTreePtr forEach)
{
forEach->SetSymbol(Symbol::ForEach);
ParseInputData(forEach);
ParseExecutionLoop(forEach);
}
void AbstractCodeModel::ParseExecutionFunction(ExecutionTreePtr execution, const Slot& outSlot)
{
/**
* \note This is the most complicated parse, but only due to the nature of our custom nodes.
* Custom nodes can trigger execution out of a node from an internal C++ class. Outside of
* SC Grammar flow of control nodes (e.g If nodes), this needs manual parsing of child nodes mildly earlier in the
* SC Graph traversal process than other nodes. Refactoring this step into a pure, depth-first, recursive
* function would be complicated and probably not worth the effort. The final output of the parser does
* yield a model that, when translated, does benefit from a simple depth first traversal of grammar nodes,
* as is done in the translator(s).
*/
AccountForEBusConnectionControl(execution);
if (execution->GetChildrenCount() == 0)
{
execution->AddChild({ &outSlot, {}, nullptr });
}
ParseMultiExecutionPre(execution);
if (!execution->IsInputOutputPreprocessed())
{
ParseOutputData(execution, execution->ModChild(0));
// look up the tree for properly routed data, preprocess variable names, and report errors or auto created variables
ParseInputData(execution);
}
ParseOperatorArithmetic(execution);
// \todo Infinite loop handling both in code and in the graph will have to occur here.
auto executionOutNodes = execution->GetId().m_node->GetConnectedNodes(outSlot);
auto numConnections = executionOutNodes.size();
if (numConnections == 0)
{
if (execution->GetChild(0).m_output.empty())
{
execution->ModChild(0).m_execution = CreateChildPlaceHolder(execution);
}
else
{
execution->ModChild(0).m_execution = CreateChildDebugMarker(execution);
}
}
else if (numConnections == 1)
{
ParseExecutionFunctionRecurse(execution, execution->ModChild(0), outSlot, executionOutNodes[0]);
}
else
{
AZStd::vector<const Slot*> outSlots;
outSlots.resize(executionOutNodes.size(), &outSlot);
ParseExecutionMultipleOutSyntaxSugar(execution, executionOutNodes, outSlots);
}
ParseMultiExecutionPost(execution);
}
void AbstractCodeModel::ParseExecutionFunctionRecurse(ExecutionTreePtr execution, ExecutionChild& executionChild, const Slot& outSlot, const AZStd::pair<const Node*, const Slot*>& nodeAndSlot)
{
// if the node is null, a validation error will be added, or has been already
if (const auto node = nodeAndSlot.first)
{
ExecutionTreePtr child = CreateChild(execution, node, nodeAndSlot.second);
executionChild.m_execution = child;
executionChild.m_slot = &outSlot;
if (IsFlowControl(child))
{
ParseExecutionTreeBody(child, *nodeAndSlot.second);
}
else
{
auto childOutSlotsOutcome = child->GetId().m_node->GetSlotsInExecutionThreadByType(*(child->GetId().m_slot), CombinedSlotType::ExecutionOut);
if (childOutSlotsOutcome.IsSuccess())
{
const auto& childOutSlots = childOutSlotsOutcome.GetValue();
AZ_Assert(!childOutSlots.empty(), "there must be an immediate out");
if (childOutSlots.size() == 1)
{
ParseExecutionTreeBody(child, *childOutSlots[0]);
}
else
{
// Interior node branches: This is required for highly custom or state-ful nodes, namely those that fire different,
// and/or unknown-at-compile-time outs based on the same in.
auto iter = m_nodeablesByNode.find(node);
if (iter == m_nodeablesByNode.end())
{
if (azrtti_cast<const Nodes::Core::Method*>(node))
{
// Method should have only one execution out, this node is out of date and unsupported by new backend
AddError(nullptr, aznew NodeCompatiliblity::NodeOutOfDate(node->GetEntityId(), node->GetNodeName()));
}
else
{
AddError(node->GetEntityId(), execution, ScriptCanvas::ParseErrors::CustomParsingRequiredForVariable);
}
return;
}
child->SetNodeable(iter->second->m_nodeable);
for (auto& childOutSlot : childOutSlots)
{
AZ_Assert(childOutSlot, "null slot in child out slot list");
ExecutionTreePtr internalOut = OpenScope(child, node, childOutSlot);
const size_t outIndex = node->GetOutIndex(*childOutSlot);
if (outIndex == std::numeric_limits<size_t>::max())
{
AddError(execution, aznew Internal::ParseError(node->GetEntityId(), AZStd::string::format("Missing internal out key for slot %s", childOutSlot->GetName().c_str())));
return;
}
internalOut->SetOutCallIndex(outIndex);
internalOut->MarkInternalOut();
internalOut->SetSymbol(Symbol::FunctionDefinition);
auto outNameOutcome = node->GetInternalOutKey(*childOutSlot);
AZ_Assert(outNameOutcome.IsSuccess(), "GetInternalOutKey failed");
internalOut->SetName(outNameOutcome.TakeValue().data());
ParseExecutionTreeBody(internalOut, *childOutSlot);
if (internalOut->GetChildrenCount() > 0)
{
child->AddChild({ childOutSlot, {}, internalOut });
}
}
ParseInputData(child);
ParseMetaData(child);
if (auto metaData = child->ModMetaData())
{
metaData->PostParseExecutionTreeBody(*this, child);
}
}
}
else
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), childOutSlotsOutcome.TakeError()));
}
}
}
else
{
AZ_Assert(false, "Child out not connected to node");
}
}
void AbstractCodeModel::ParseExecutionIfStatement(ExecutionTreePtr executionIf)
{
bool isCheckedOperation = false;
bool callCheckedOpOnBothBranches = false;
if (executionIf->GetId().m_node->IsIfBranchPrefacedWithBooleanExpression())
{
auto removeChildOutcome = RemoveChild(executionIf->ModParent(), executionIf);
if (!removeChildOutcome.IsSuccess())
{
AddError(executionIf->GetNodeId(), executionIf, ScriptCanvas::ParseErrors::FailedToRemoveChild);
}
if (!IsErrorFree())
{
return;
}
const auto indexAndChild = removeChildOutcome.TakeValue();
ExecutionTreePtr booleanExpression = CreateChild(executionIf->ModParent(), executionIf->GetId().m_node, executionIf->GetId().m_slot);
executionIf->ModParent()->InsertChild(indexAndChild.first, { indexAndChild.second.m_slot, indexAndChild.second.m_output, booleanExpression });
executionIf->SetParent(booleanExpression);
// make a condition here
auto symbol = CheckLogicalExpressionSymbol(booleanExpression);
if (symbol != Symbol::FunctionCall && symbol != Symbol::Count)
{
ParseExecutionLogicalExpression(booleanExpression, symbol);
}
else if (auto methodNode = azrtti_cast<const Nodes::Core::Method*>(executionIf->GetId().m_node))
{
if (methodNode->BranchesOnResult())
{
const Data::Type methodResultType = methodNode->GetResultType();
if (methodResultType == Data::Type::Boolean())
{
// result type is boolean, parse it as boolean expression directly
ParseExecutionIfStatementInternalFunction(booleanExpression);
}
else
{
auto removeChildOutcome2 = RemoveChild(booleanExpression->ModParent(), booleanExpression);
if (!removeChildOutcome2.IsSuccess())
{
AddError(methodNode->GetEntityId(), executionIf, ScriptCanvas::ParseErrors::FailedToRemoveChild);
}
if (!IsErrorFree())
{
return;
}
const auto indexAndChild2 = removeChildOutcome.TakeValue();
// parse if statement internal function
ExecutionTreePtr internalFunction = CreateChild(booleanExpression->ModParent(), booleanExpression->GetId().m_node, booleanExpression->GetId().m_slot);
booleanExpression->ModParent()->InsertChild(indexAndChild2.first, { indexAndChild2.second.m_slot, indexAndChild2.second.m_output, internalFunction });
booleanExpression->SetParent(internalFunction);
ParseExecutionIfStatementInternalFunction(internalFunction);
internalFunction->ModChild(0).m_execution = booleanExpression;
booleanExpression->ClearInput();
const auto& logicOutput = internalFunction->ModChild(0).m_output;
booleanExpression->AddInput({ nullptr, logicOutput[0].second->m_source, DebugDataSource::FromInternal() });
// parse if statement boolean expression
ParseBranchOnResultFunctionCheck(booleanExpression);
}
}
else if (methodNode->IsCheckedOperation(&callCheckedOpOnBothBranches))
{
isCheckedOperation = true;
ParseCheckedFunctionCheck(booleanExpression);
}
else
{
AddError(methodNode->GetEntityId(), executionIf, ScriptCanvas::ParseErrors::FailedToParseIfBranch);
}
}
booleanExpression->ModChild(0).m_execution = executionIf;
// route the output of the logical expression to the if condition grammar node
// and the same output to the output of the if condition grammar node
executionIf->ClearInput();
const auto& logicOutput = booleanExpression->ModChild(0).m_output;
executionIf->AddInput({ nullptr, logicOutput[0].second->m_source, DebugDataSource::FromInternal() });
}
else
{
ParseInputData(executionIf);
}
auto outSlotTrue = executionIf->GetId().m_node->GetIfBranchTrueOutSlot();
auto outSlotFalse = executionIf->GetId().m_node->GetIfBranchFalseOutSlot();
executionIf->SetSymbol(Symbol::IfCondition);
ExecutionTreePtr executionTrue = OpenScope(executionIf, executionIf->GetId().m_node, outSlotTrue);
executionIf->AddChild({ outSlotTrue, {}, executionTrue });
if (isCheckedOperation)
{
executionTrue->SetSymbol(Symbol::FunctionCall);
ParseMetaData(executionTrue);
ParseExecutionFunction(executionTrue, *outSlotTrue);
if (auto metaData = executionTrue->ModMetaData())
{
metaData->PostParseExecutionTreeBody(*this, executionTrue);
}
}
else
{
executionTrue->SetSymbol(Symbol::PlaceHolderDuringParsing);
executionTrue->MarkInputOutputPreprocessed();
if (outSlotTrue)
{
ParseExecutionTreeBody(executionTrue, *outSlotTrue);
}
else
{
AddError(AZ::EntityId(), executionTrue, ScriptCanvas::ParseErrors::MissingTrueExecutionSlotOnIf);
}
executionTrue->MarkDebugEmptyStatement();
}
ExecutionTreePtr executionFalse = OpenScope(executionIf, executionIf->GetId().m_node, outSlotFalse);
executionIf->AddChild({ outSlotFalse, {}, executionFalse });
if (isCheckedOperation && callCheckedOpOnBothBranches)
{
executionFalse->SetSymbol(Symbol::FunctionCall);
ParseMetaData(executionFalse);
ParseExecutionFunction(executionFalse, *outSlotFalse);
if (auto metaData = executionFalse->ModMetaData())
{
metaData->PostParseExecutionTreeBody(*this, executionFalse);
}
}
else
{
executionFalse->SetSymbol(Symbol::PlaceHolderDuringParsing);
executionFalse->MarkInputOutputPreprocessed();
if (outSlotFalse)
{
ParseExecutionTreeBody(executionFalse, *outSlotFalse);
}
else
{
AddError(AZ::EntityId(), executionFalse, ScriptCanvas::ParseErrors::MissingFalseExecutionSlotOnIf);
}
executionFalse->MarkDebugEmptyStatement();
}
}
void AbstractCodeModel::ParseExecutionIfStatementBooleanExpression(ExecutionTreePtr booleanExpressionExecution, AZStd::string executionName, LexicalScope lexicalScope)
{
booleanExpressionExecution->SetName(executionName);
booleanExpressionExecution->SetNameLexicalScope(lexicalScope);
booleanExpressionExecution->AddChild({});
ExecutionChild& child = booleanExpressionExecution->ModChild(0);
VariableConstPtr result = AZStd::make_shared<Variable>();
result->m_name = booleanExpressionExecution->ModScope()->AddVariableName(executionName, "result");
result->m_datum.SetType(Data::Type::Boolean());
result->m_source = booleanExpressionExecution;
auto outputAssignment = CreateOutputAssignment(result);
child.m_output.push_back(AZStd::make_pair(nullptr, outputAssignment));
booleanExpressionExecution->SetSymbol(Symbol::FunctionCall);
}
void AbstractCodeModel::ParseExecutionIfStatementInternalFunction(ExecutionTreePtr internalFunctionExecution)
{
ParseMetaData(internalFunctionExecution);
internalFunctionExecution->SetSymbol(Symbol::FunctionCall);
internalFunctionExecution->AddChild({});
ParseOutputData(internalFunctionExecution, internalFunctionExecution->ModChild(0));
ParseInputData(internalFunctionExecution);
if (auto metaData = internalFunctionExecution->ModMetaData())
{
metaData->PostParseExecutionTreeBody(*this, internalFunctionExecution);
}
}
void AbstractCodeModel::ParseExecutionLogicalExpression(ExecutionTreePtr execution, Symbol symbol)
{
if (!execution->GetId().m_node->IsIfBranchPrefacedWithBooleanExpression())
{
AddError(AZ::EntityId(), execution, ScriptCanvas::ParseErrors::AttemptToParseNonExpression);
return;
}
execution->AddChild({});
ParseOutputData(execution, execution->ModChild(0));
execution->SetSymbol(symbol);
if (!IsLogicalExpression(execution))
{
AddError(AZ::EntityId(), execution, ScriptCanvas::ParseErrors::FailedToDeduceExpression);
}
ParseInputData(execution);
}
void AbstractCodeModel::ParseBranchOnResultFunctionCheck(ExecutionTreePtr execution)
{
if (auto methodNode = azrtti_cast<const Nodes::Core::Method*>(execution->GetId().m_node))
{
AZStd::string checkFunctionName;
LexicalScope lexicalScope;
if (methodNode->GetBranchOnResultCheckName(checkFunctionName, lexicalScope))
{
ParseExecutionIfStatementBooleanExpression(execution, checkFunctionName, lexicalScope);
}
else
{
AZ_Assert(false, "Unable to fetch branch on result check function name.");
}
}
else
{
AZ_Assert(false, "Function check attempted on a node that wasn't a method node.");
}
}
void AbstractCodeModel::ParseCheckedFunctionCheck(ExecutionTreePtr execution)
{
if (auto methodNode = azrtti_cast<const Nodes::Core::Method*>(execution->GetId().m_node))
{
AZ::CheckedOperationInfo checkedOpInfo;
AZStd::string checkedOpExposedName;
LexicalScope lexicalScope;
// manually process as if the check itself was a method node
if (methodNode->GetCheckedOperationInfo(checkedOpInfo, checkedOpExposedName, lexicalScope))
{
ParseExecutionIfStatementBooleanExpression(execution, checkedOpExposedName, lexicalScope);
ParseInputData(execution);
execution->ReduceInputSet(checkedOpInfo.m_inputRestriction);
return;
}
else
{
AZ_Assert(false, "No checked information operation in execution declared to have one.");
}
}
else
{
AZ_Assert(false, "Function check attempted on a node that wasn't a method node.");
}
}
void AbstractCodeModel::ParseExecutionMultipleOutSyntaxSugarOfSequencNode(ExecutionTreePtr execution)
{
auto sequentialOutcome = execution->GetId().m_node->GetSlotsInExecutionThreadByType(*(execution->GetId().m_slot), CombinedSlotType::ExecutionOut);
if (!sequentialOutcome.IsSuccess())
{
AddError(execution, aznew Internal::ParseError(execution->GetId().m_node->GetEntityId(), "sequential execution slot mapping failure"));
return;
}
AZStd::vector<const Slot*>& childOutSlots = sequentialOutcome.GetValue();
if (!childOutSlots.empty())
{
// now gather all the connections to sequence node out slots, and treat them like
// (ordered) connections to the out that connected to the sequence node
EndpointsResolved outNodes;
AZStd::vector<const Slot*> outSlots;
for (auto outSlot : childOutSlots)
{
if (outSlot)
{
auto executionOutNodes = execution->GetId().m_node->GetConnectedNodes(*outSlot);
outNodes.insert(outNodes.end(), executionOutNodes.begin(), executionOutNodes.end());
outSlots.insert(outSlots.end(), executionOutNodes.size(), outSlot);
}
}
ParseExecutionMultipleOutSyntaxSugar(execution, outNodes, outSlots);
}
}
void AbstractCodeModel::ParseExecutionMultipleOutSyntaxSugar(ExecutionTreePtr execution, const EndpointsResolved& executionOutNodes, const AZStd::vector<const Slot*>& outSlots)
{
const auto executionNodeId = execution->GetId().m_node ? execution->GetId().m_node->GetEntityId() : AZ::EntityId();
if (executionOutNodes.size() != outSlots.size())
{
AddError(executionNodeId, execution, ParseErrors::ParseExecutionMultipleOutSyntaxSugarMismatchOutSize);
}
if (execution->GetSymbol() != Symbol::Sequence)
{
// make a new execution node, add it the the child out indicated by the slot
// set execution equal to the new node
ExecutionTreePtr sequence = CreateChild(execution, nullptr, outSlots[0]);
sequence->SetSymbol(Symbol::Sequence);
ExecutionChild* child = execution->FindChild(outSlots[0]->GetId());
if (!child)
{
AddError(executionNodeId, execution, ParseErrors::ParseExecutionMultipleOutSyntaxSugarNullChildFound);
return;
}
if (child->m_execution)
{
AddError(executionNodeId, execution, ParseErrors::ParseExecutionMultipleOutSyntaxSugarNonNullChildExecutionFound);
return;
}
child->m_execution = sequence;
execution = sequence;
}
for (size_t childIndex = 0; childIndex < executionOutNodes.size(); ++childIndex)
{
execution->AddChild({ outSlots[childIndex], {}, nullptr });
}
for (size_t childIndex = 0; childIndex < executionOutNodes.size(); ++childIndex)
{
ParseExecutionFunctionRecurse(execution, execution->ModChild(childIndex), *outSlots[childIndex], executionOutNodes[childIndex]);
if (!IsErrorFree())
{
return;
}
if (execution->GetChildrenCount() != executionOutNodes.size())
{
AddError(executionNodeId, execution, ParseErrors::ParseExecutionMultipleOutSyntaxSugarChildExecutionRemovedAndNotReplaced);
return;
}
}
}
void AbstractCodeModel::ParseExecutionOnce(ExecutionTreePtr once)
{
const auto ID = once->GetId();
auto onceVariableIter = m_controlVariablesBySourceNode.find(ID.m_node);
AZ_Assert(onceVariableIter != m_controlVariablesBySourceNode.end(), "Once node didn't add a control variable to graph scope");
ExecutionTreePtr nextParse;
const Slot* nextSlot = nullptr;
if (IsOnceIn(*ID.m_node, ID.m_slot))
{
auto outSlotTrue = GetOnceOutSlot(*ID.m_node);
AZ_Assert(outSlotTrue, "Must be an out slot for a Once node");
once->SetSymbol(Symbol::IfCondition);
once->AddInput({ nullptr, onceVariableIter->second, DebugDataSource::FromInternal() });
// the once branch, first set the control value to false
ExecutionTreePtr controlValue = OpenScope(once, once->GetId().m_node, outSlotTrue);
controlValue->SetSymbol(Symbol::VariableAssignment);
auto falseValue = AZStd::make_shared<Variable>();
falseValue->m_isMember = false;
falseValue->m_source = controlValue;
falseValue->m_datum = Datum(Data::BooleanType(false));
controlValue->AddInput({ nullptr, falseValue, DebugDataSource::FromInternal() });
once->AddChild({ outSlotTrue, {}, controlValue });
// placeholder true branch
ExecutionTreePtr executionTrue = CreateChild(controlValue, once->GetId().m_node, outSlotTrue);
executionTrue->SetSymbol(Symbol::PlaceHolderDuringParsing);
executionTrue->MarkInputOutputPreprocessed();
controlValue->AddChild({ outSlotTrue, {}, executionTrue });
controlValue->ModChild(0).m_output.push_back({ nullptr, CreateOutputAssignment(onceVariableIter->second) });
nextParse = executionTrue;
nextSlot = outSlotTrue;
// (unused) placeholder false branch for possible debugging, and for if statement construction consistency
ExecutionTreePtr neverRuns = OpenScope(once, once->GetId().m_node, nullptr);
neverRuns->MarkDebugEmptyStatement();
once->AddChild({ nullptr, {}, neverRuns });
}
else
{
AZ_Assert(IsOnceReset(*ID.m_node, ID.m_slot), "Once slot not accounted for in grammar");
auto onceResetSlot = GetOnceOnResetSlot(*ID.m_node);
AZ_Assert(onceResetSlot, "Must be an On Reset Slot for a Once node");
once->SetSymbol(Symbol::VariableAssignment);
auto trueValue = AZStd::make_shared<Variable>();
trueValue->m_isMember = false;
trueValue->m_source = once;
trueValue->m_datum = Datum(Data::BooleanType(true));
once->AddInput({ nullptr, trueValue, DebugDataSource::FromInternal() });
ExecutionTreePtr onReset = CreateChild(once, once->GetId().m_node, onceResetSlot);
onReset->SetSymbol(Symbol::PlaceHolderDuringParsing);
onReset->MarkInputOutputPreprocessed();
once->AddChild({ onceResetSlot, {}, onReset });
once->ModChild(0).m_output.push_back({ nullptr, CreateOutputAssignment(onceVariableIter->second) });
nextParse = onReset;
nextSlot = onceResetSlot;
}
ParseExecutionTreeBody(nextParse, *nextSlot);
nextParse->MarkDebugEmptyStatement();
}
void AbstractCodeModel::ParseExecutionRandomSwitchStatement(ExecutionTreePtr executionRandomSwitch)
{
// parse input normally
ParseInputData(executionRandomSwitch);
// add the weight names for later summation
const size_t weightCount = executionRandomSwitch->GetInputCount();
for (size_t index = 0; index < weightCount; ++index)
{
auto weight = AZStd::make_shared<Variable>();
weight->m_name = executionRandomSwitch->ModScope()->AddVariableName(AZStd::string::format("randomSwitchWeight_%zu", index));
weight->m_source = executionRandomSwitch;
executionRandomSwitch->AddInput({ nullptr, weight, DebugDataSource::FromInternal() });
}
// add a control variable for later comparison against weight
auto controlVariable = AZStd::make_shared<Variable>();
controlVariable->m_datum = Datum(Data::NumberType(0));
controlVariable->m_name = executionRandomSwitch->ModScope()->AddVariableName("randomSwitchControl");
controlVariable->m_source = executionRandomSwitch;
executionRandomSwitch->AddInput({ nullptr, controlVariable, DebugDataSource::FromInternal() });
// add a running total variable for ease of use
auto runningTotal = AZStd::make_shared<Variable>();
runningTotal->m_datum = Datum(Data::NumberType(0));
runningTotal->m_name = executionRandomSwitch->ModScope()->AddVariableName("randomSwitchRunningTotal");
runningTotal->m_source = executionRandomSwitch;
executionRandomSwitch->AddInput({ nullptr, runningTotal, DebugDataSource::FromInternal() });
executionRandomSwitch->SetSymbol(Symbol::RandomSwitch);
ParseExecutionSequentialChildren(executionRandomSwitch);
}
void AbstractCodeModel::ParseExecutionSequentialChildren(ExecutionTreePtr execution)
{
auto sequentialOutcome = execution->GetId().m_node->GetSlotsInExecutionThreadByType(*(execution->GetId().m_slot), CombinedSlotType::ExecutionOut);
if (!sequentialOutcome.IsSuccess())
{
AddError(AZ::EntityId(), execution, ScriptCanvas::ParseErrors::SequentialExecutionMappingFailure);
return;
}
const auto& childOuts = sequentialOutcome.GetValue();
for (const auto& childOutSlot : childOuts)
{
ExecutionTreePtr childOutExecution = OpenScope(execution, execution->GetId().m_node, childOutSlot);
childOutExecution->SetSymbol(Symbol::PlaceHolderDuringParsing);
childOutExecution->MarkInputOutputPreprocessed();
execution->AddChild({ childOutSlot, {}, childOutExecution });
ParseExecutionTreeBody(childOutExecution, *childOutSlot);
childOutExecution->MarkDebugEmptyStatement();
};
if (execution->GetChildrenCount() == 0)
{
execution->SetSymbol(Symbol::PlaceHolderDuringParsing);
}
}
void AbstractCodeModel::ParseExecutionSwitchStatement(ExecutionTreePtr executionSwitch)
{
ParseInputData(executionSwitch);
executionSwitch->SetSymbol(Symbol::Switch);
ParseExecutionSequentialChildren(executionSwitch);
}
// the execution will already have its function definition defined, this will create the body of that function
void AbstractCodeModel::ParseExecutionTreeBody(ExecutionTreePtr execution, const Slot& outSlot)
{
ParseMetaData(execution);
// \note this grammar check matches IsFlowControl, and needs to be merged with and replace ParseExecutionFunctionRecurse
if (IsBreak(execution))
{
ParseExecutionBreak(execution);
}
else if (IsUserOutNode(execution))
{
ParseUserOutCall(execution);
}
else if (IsIfCondition(execution))
{
ParseExecutionIfStatement(execution);
}
else if (IsForEach(execution))
{
ParseExecutionForEachStatement(execution);
}
else if (IsSequenceNode(execution))
{
execution->SetSymbol(Symbol::Sequence);
ParseExecutionMultipleOutSyntaxSugarOfSequencNode(execution);
}
else if (IsSwitchStatement(execution))
{
ParseExecutionSwitchStatement(execution);
}
else if (IsCycle(execution))
{
ParseExecutionCycleStatement(execution);
}
else if (IsOnce(execution))
{
ParseExecutionOnce(execution);
}
else if (IsRandomSwitchStatement(execution))
{
ParseExecutionRandomSwitchStatement(execution);
}
else if (IsWhileLoop(execution))
{
ParseExecutionWhileLoop(execution);
}
else
{
ParseExecutionFunction(execution, outSlot);
// if is a connect call to an az event handler...mark grammar, because it will require special processing
// ParseEventConnectionHandling(execution, outSlot); .. add the previously executed function to the second child of the node
// make a new grammar thing
}
if (auto metaData = execution->ModMetaData())
{
metaData->PostParseExecutionTreeBody(*this, execution);
}
}
ExecutionTreePtr AbstractCodeModel::ParseExecutionTreeRoot(ExecutionTreePtr root)
{
if (auto slot = root->GetId().m_slot)
{
ParseMetaData(root);
ParseExecutionTreeBody(root, *slot);
if (root->GetChildrenCount() > 0)
{
root->ModChild(0).m_execution->MarkDebugEmptyStatement();
PostParseProcess(root);
PostParseErrorDetect(root);
if (IsErrorFree())
{
return root;
}
}
else
{
AddError(slot->GetNodeId(), root, ScriptCanvas::ParseErrors::NoChildrenAfterRoot);
}
}
else
{
AddError(AZ::EntityId(), root, ScriptCanvas::ParseErrors::NoOutForExecution);
}
root->Clear();
return nullptr;
}
ExecutionTreePtr AbstractCodeModel::ParseExecutionTreeRoot(const Node& node, const Slot& outSlot, MarkLatent markLatent)
{
if (ExecutionContainsCyclesCheck(node, outSlot))
{
return nullptr;
}
ExecutionTreePtr root = OpenScope(nullptr, &node, &outSlot);
root->SetSymbol(Symbol::FunctionDefinition);
if (outSlot.IsLatent() || markLatent == MarkLatent::Yes)
{
root->MarkRootLatent();
}
return ParseExecutionTreeRoot(root);
}
void AbstractCodeModel::ParseExecutionTreeRoots(const Node& node)
{
auto nodeableParseIter = m_nodeablesByNode.find(&node);
if (nodeableParseIter != m_nodeablesByNode.end())
{
auto latentSlots = node.GetSlotsByType(CombinedSlotType::LatentOut);
for (const auto slot : latentSlots)
{
if (slot)
{
if (auto outRoot = ParseExecutionTreeRoot(node, *slot, MarkLatent::Yes))
{
AddDebugInfiniteLoopDetectionInHandler(outRoot);
outRoot->SetNodeable(nodeableParseIter->second->m_nodeable);
auto latentOutKeyOutcome = node.GetLatentOutKey(*slot);
if (latentOutKeyOutcome.IsSuccess())
{
const size_t outIndex = node.GetOutIndex(*slot);
if (outIndex == std::numeric_limits<size_t>::max())
{
AddError(outRoot, aznew Internal::ParseError(node.GetEntityId(), AZStd::string::format("Missing internal out key for slot %s", slot->GetName().c_str())));
return;
}
outRoot->SetOutCallIndex(outIndex);
outRoot->SetName(latentOutKeyOutcome.GetValue().data());
AZStd::const_pointer_cast<NodeableParse>(nodeableParseIter->second)->m_latents.emplace_back(outRoot->GetName(), outRoot);
}
else
{
AddError(node.GetEntityId(), nullptr, AZStd::string::format("GetLatentOutKey failed for nodeable failed: %s", node.GetDebugName().data()));
return;
}
}
}
else
{
AddError(node.GetEntityId(), nullptr, AZStd::string::format("null latent slot returned by node: %s", node.GetDebugName().data()));
return;
}
}
// \todo more work will be required to determine if a nodeable node is wasted or not
}
auto ebusEventHandlingIter = m_ebusHandlingByNode.find(&node);
if (ebusEventHandlingIter != m_ebusHandlingByNode.end())
{
const auto eventSlots = node.GetEventSlots();
for (const auto slot : eventSlots)
{
if (slot)
{
if (auto eventRoot = ParseExecutionTreeRoot(node, *slot, MarkLatent::Yes))
{
AddDebugInfiniteLoopDetectionInHandler(eventRoot);
auto latentOutKeyOutcome = node.GetInternalOutKey(*slot);
if (latentOutKeyOutcome.IsSuccess())
{
eventRoot->SetName(latentOutKeyOutcome.GetValue().data());
AZStd::const_pointer_cast<EBusHandling>(ebusEventHandlingIter->second)->m_events.emplace_back(eventRoot->GetName(), eventRoot);
}
else
{
AddError(node.GetEntityId(), nullptr, AZStd::string::format("GetInternalOutKey for ebus handler failed: %s", node.GetDebugName().data()));
return;
}
}
}
else
{
AddError(node.GetEntityId(), nullptr, AZStd::string::format("null event slot returned by event handler: %s", node.GetDebugName().data()));
return;
}
}
if (ebusEventHandlingIter->second->m_events.empty())
{
// \todo add a note or warning about empty events, especially if the connection controls are used, or automatically disable.
// remove the handler, remove the connection control calls, (for no-ops) or something?
}
}
auto eventHandlingIter = m_eventHandlingByNode.find(&node);
if (eventHandlingIter != m_eventHandlingByNode.end())
{
const auto eventSlots = node.GetEventSlots();
for (const auto slot : eventSlots)
{
if (slot)
{
if (auto eventRoot = ParseExecutionTreeRoot(node, *slot, MarkLatent::Yes))
{
AddDebugInfiniteLoopDetectionInHandler(eventRoot);
auto latentOutKeyOutcome = node.GetInternalOutKey(*slot);
if (latentOutKeyOutcome.IsSuccess())
{
eventRoot->SetName(latentOutKeyOutcome.GetValue().data());
AZStd::const_pointer_cast<EventHandling>(eventHandlingIter->second)->m_eventHandlerFunction = eventRoot;
}
else
{
AddError(node.GetEntityId(), nullptr, AZStd::string::format("GetInternalOutKey for event handler failed: %s", node.GetDebugName().data()));
return;
}
}
}
else
{
AddError(node.GetEntityId(), nullptr, AZStd::string::format("null event slot returned by event handler: %s", node.GetDebugName().data()));
return;
}
}
}
for (auto dataSlot : node.GetOnVariableHandlingDataSlots())
{
auto variableWriteHandlingIter = m_variableWriteHandlingBySlot.find(dataSlot);
if (variableWriteHandlingIter != m_variableWriteHandlingBySlot.end())
{
VariableWriteHandlingPtr variableWriteHandling = AZStd::const_pointer_cast<VariableWriteHandling>(variableWriteHandlingIter->second);
if (node.IsVariableWriteHandler())
{
const auto eventSlots = node.GetEventSlots();
AZ_Assert(eventSlots.size() == 1, "no variable change slot");
if (auto onVariableWrite = ParseExecutionTreeRoot(node, *eventSlots[0], MarkLatent::No))
{
auto name = m_graphScope->AddFunctionName(AZStd::string::format("On%sWritten", variableWriteHandling->m_variable->m_name.c_str()).c_str());
onVariableWrite->SetName(name);
variableWriteHandling->m_function = onVariableWrite;
onVariableWrite->MarkInfiniteLoopDetectionPoint();
auto variable = AddMemberVariable(Datum(Data::Type::Number(), Datum::eOriginality::Original), "variableChangeIterationCounter");
variable->m_isDebugOnly = true;
m_implicitVariablesByNode.insert({ onVariableWrite, variable });
}
}
}
}
if (const Nodes::Core::FunctionDefinitionNode* nodeling = azrtti_cast<const Nodes::Core::FunctionDefinitionNode*>(&node))
{
auto userFunctionIter = m_userInsThatRequireTopology.find(nodeling);
if (userFunctionIter != m_userInsThatRequireTopology.end())
{
auto& userFunctionNode = *userFunctionIter->first;
auto outSlots = userFunctionNode.GetSlotsByType(CombinedSlotType::ExecutionOut);
if (outSlots.empty() || !outSlots.front())
{
AddError(userFunctionNode.GetEntityId(), nullptr, ScriptCanvas::ParseErrors::NoOutSlotInFunctionDefinitionStart);
return;
}
if (!ExecutionContainsCyclesCheck(userFunctionNode, *outSlots.front()))
{
auto definition = userFunctionIter->second;
auto entrySlot = definition->GetId().m_slot;
AZ_Assert(entrySlot, "Bad accounting in user function definition node");
AZStd::vector<VariablePtr> returnValues;
UserOutCallCollector userOutCallCollector;
TraverseExecutionConnections(userFunctionNode, *entrySlot, userOutCallCollector);
const AZStd::unordered_set<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>& uniqueNodelingsOut = userOutCallCollector.GetOutCalls();
for (const auto& returnCall : uniqueNodelingsOut)
{
for (const auto returnSlot : returnCall->GetSlotsByType(CombinedSlotType::DataIn))
{
auto iter = m_outputVariableByNodelingOutSlot.find(returnSlot);
if (iter != m_outputVariableByNodelingOutSlot.end())
{
VariablePtr variable = iter->second;
variable->m_name = definition->ModScope()->AddVariableName(returnSlot->GetName());
variable->m_source = definition;
variable->m_sourceSlotId = returnSlot->GetId();
returnValues.push_back(variable);
}
}
}
m_returnValuesByUserFunctionDefinition.insert(AZStd::make_pair(nodeling, ReturnValueDescription{ returnValues, uniqueNodelingsOut.size() }));
if (auto root = ParseExecutionTreeRoot(userFunctionIter->second))
{
m_functions.push_back(root);
}
else
{
m_userInsThatRequireTopology.erase(userFunctionIter);
}
}
}
}
}
void AbstractCodeModel::ParseExecutionWhileLoop(ExecutionTreePtr execution)
{
execution->SetSymbol(Symbol::While);
ParseInputData(execution);
ParseExecutionLoop(execution);
}
void AbstractCodeModel::ParseImplicitVariables(const Node& node)
{
if (IsCycle(node))
{
auto cycleVariable = AddMemberVariable(Datum(Data::NumberType(0)), "cycleControl");
m_controlVariablesBySourceNode.insert({ &node, cycleVariable });
}
else if (IsOnce(node))
{
auto onceControl = AddMemberVariable(Datum(Data::BooleanType(true)), "onceControl");
m_controlVariablesBySourceNode.insert({ &node, onceControl });
}
else
{
const auto nodelingType = CheckNodelingType(node);
if (nodelingType != NodelingType::None)
{
ParseNodelingVariables(node, nodelingType);
}
}
}
void AbstractCodeModel::ParseInputData(ExecutionTreePtr execution)
{
if (execution->GetSymbol() == Symbol::FunctionDefinition)
{
// input data for functions has been handled already
return;
}
// special handling for Extraction nodes
if (IsExecutedPropertyExtraction(execution))
{
// the input will be assigned by the parent node in the extraction
return;
}
// special handling for Get Variable nodes
else if (IsVariableGet(execution))
{
const VariableId assignedFromId = execution->GetId().m_node->GetVariableIdRead(execution->GetId().m_slot);
auto variableRead = assignedFromId.IsValid() ? FindVariable(assignedFromId) : nullptr;
if (variableRead)
{
// nullptr is acceptable here
execution->AddInput({ nullptr, variableRead, DebugDataSource::FromVariable(SlotId{}, variableRead->m_datum.GetType(), variableRead->m_sourceVariableId) });
}
else
{
AddError(execution->GetNodeId(), execution, ScriptCanvas::ParseErrors::MissingVariable);
}
}
else
{
auto dataSlotsOutcome = execution->GetId().m_node->GetSlotsInExecutionThreadByType(*(execution->GetId().m_slot), CombinedSlotType::DataIn);
if (dataSlotsOutcome.IsSuccess())
{
if (ParseInputThisPointer(execution))
{
const auto& dataSlots = dataSlotsOutcome.GetValue();
for (auto dataInSlot : dataSlots)
{
AZ_Assert(dataInSlot, "data corruption, bad input slot");
ParseInputDatum(execution, *dataInSlot);
}
}
}
else
{
AddError(nullptr, aznew Internal::ParseError(execution->GetNodeId(), dataSlotsOutcome.TakeError()));
}
}
}
void AbstractCodeModel::ParseInputDatum(ExecutionTreePtr execution, const Slot& input)
{
// \todo look for crossed lines in inferred functions, because sometimes, rather than the input
// being named of the result of the output that emitted it, it will be the name of the inferred function
// parameter ---> make a map of node output to function input names
AZ_Assert(execution->GetSymbol() != Symbol::FunctionDefinition, "Function definition input should have been handled already");
auto nodes = execution->GetId().m_node->GetConnectedNodes(input);
if (nodes.empty())
{
if (auto variable = FindReferencedVariableChecked(execution, input))
{
execution->AddInput({ &input, variable, DebugDataSource::FromVariable(input.GetId(), input.GetDataType(), variable->m_sourceVariableId) });
CheckConversion(execution->ModConversions(), variable, execution->GetInputCount() - 1, input.GetDataType());
}
// This concept may never actually be possible
// else if (RequiresCreationFunction(input.GetDataType().GetType()))
// {
// AddError(execution, aznew NotYetImplemented(
// "1: finish input created by name when connected to other nodes"
// "2: add the name to the scope"
// "3: and check inputs be re-used, common constructors like zero/1, etc"
// "4: read the variable name if it is present instead of creating it"
// "5: check for entity references to self and other member slice variables"
// "6: mark the variable with RequiredCreationFunction()"));
// }
else
{
auto variableDatum = input.FindDatum();
AZ_Assert(variableDatum, "Input datum missing from Slot %s on Node %s", input.GetName().data(), execution->GetId().m_node ? execution->GetId().m_node->GetNodeName().data() : "");
auto inputVariable = AZStd::make_shared<Variable>();
inputVariable->m_source = execution;
inputVariable->m_sourceSlotId = input.GetId();
inputVariable->m_name = execution->ModScope()->AddVariableName(input.GetName());
if (variableDatum->GetType().IsValid())
{
inputVariable->m_datum = *variableDatum;
}
else if (execution->GetId().m_node->ConvertsInputToStrings())
{
inputVariable->m_datum = Datum(Data::Type::String(), Datum::eOriginality::Original);
}
else
{
AddError(execution->GetNodeId(), execution, AZStd::string::format("input type is invalid on Slot %s on Node %s", input.GetName().data(), execution->GetId().m_node ? execution->GetId().m_node->GetNodeName().data() : "").data());
return;
}
execution->AddInput({ &input, inputVariable, DebugDataSource::FromSelfSlot(input, inputVariable->m_datum.GetType()) });
}
}
else
{
if (auto sourceVariable = ParseConnectedInputData(input, execution, nodes, FirstNode::Parent))
{
execution->AddInput({ &input, sourceVariable, DebugDataSource::FromOtherSlot(input.GetId(), input.GetDataType(), sourceVariable->m_sourceSlotId) });
CheckConversion(execution->ModConversions(), sourceVariable, execution->GetInputCount() - 1, input.GetDataType());
}
else
{
// we don't support this, yet, but visually we could
// we could support both things, technically...auto-generated inputs, and defaults on the non-connected
// execution thread, or whatever makes possible sense
// \todo send enough information to reveal the data path in the editor
const auto& targetNode = *execution->GetId().m_node;
const auto& targetSlot = input;
for (auto sourceNodeAndSlot : nodes)
{
AddError(nullptr, aznew ScopedDataConnectionEvent
( execution->GetNodeId()
, targetNode
, targetSlot
, *sourceNodeAndSlot.first
, *sourceNodeAndSlot.second));
}
}
}
}
bool AbstractCodeModel::ParseInputThisPointer(ExecutionTreePtr execution)
{
auto node = execution->GetId().m_node;
if (node->IsVariableWriteHandler())
{
auto addressSlot = node->GetEBusConnectAddressSlot();
AZ_Assert(addressSlot, "variable write handler node must have address slot");
auto writeHandlingBySlot = m_variableWriteHandlingBySlot.find(addressSlot);
AZ_Assert(writeHandlingBySlot != m_variableWriteHandlingBySlot.end(), "bad variable write handling accounting");
auto variableHandling = writeHandlingBySlot->second;
if (!variableHandling->m_connectionVariable)
{
AZStd::string controlName = variableHandling->m_variable->m_name;
controlName.append("WriteControl");
variableHandling->m_connectionVariable = AddMemberVariable(Datum(variableHandling->m_startsConnected), controlName);
}
auto connectionValue = AZStd::make_shared<Variable>();
connectionValue->m_datum = Datum(node->GetEBusConnectSlot() == execution->GetId().m_slot);
connectionValue->m_source = execution;
execution->AddInput({ nullptr, connectionValue, DebugDataSource::FromInternal() });
execution->ModChild(0).m_output.push_back({ nullptr, CreateOutputAssignment(variableHandling->m_connectionVariable) });
execution->SetSymbol(Symbol::VariableAssignment);
return false;
}
else if (CheckEventHandlingType(*node) == EventHandingType::Event)
{
if (auto variable = FindVariable(execution->GetNodeId()))
{
execution->MarkInputHasThisPointer();
execution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() });
}
else
{
auto node2 = execution->GetId().m_node;
AddError(execution, aznew ParseError(node2->GetEntityId(), AZStd::string::format
( "Failed to find member variable for Node: %s Id: %s"
, node2->GetNodeName().data()
, node2->GetEntityId().ToString().data()).data()));
}
}
else if (node->IsEventHandler())
{
if (auto eventHandling = GetEBusEventHandling(node))
{
auto variable = AZStd::make_shared<Variable>();
variable->m_datum = Datum(eventHandling->m_handlerName);
execution->MarkInputHasThisPointer();
execution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() });
}
else
{
AddError(execution->GetNodeId(), execution, ScriptCanvas::ParseErrors::BadEventHandlingAccounting);
}
}
else if (node->IsNodeableNode())
{
if (auto variable = FindVariable(execution->GetNodeId()))
{
execution->MarkInputHasThisPointer();
execution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() });
}
else
{
auto node2 = execution->GetId().m_node;
AddError(execution, aznew ParseError(node2->GetEntityId(), AZStd::string::format
( "Failed to find member variable for Node: %s Id: %s"
, node2->GetNodeName().data()
, node2->GetEntityId().ToString().data()).data()));
}
}
return true;
}
void AbstractCodeModel::ParseMetaData(ExecutionTreePtr execution)
{
if (!execution->GetMetaData())
{
if (auto metaData = CreateMetaData(execution))
{
execution->SetMetaData(metaData);
}
}
}
void AbstractCodeModel::ParseMultiExecutionPost(ExecutionTreePtr execution)
{
ParsePropertyExtractionsPost(execution);
ParseMultipleFunctionCallPost(execution);
}
void AbstractCodeModel::ParseMultiExecutionPre(ExecutionTreePtr execution)
{
ParsePropertyExtractionsPre(execution);
}
void AbstractCodeModel::ParseMultipleFunctionCallPost(ExecutionTreePtr execution)
{
auto& id = execution->GetId();
MultipleFunctionCallFromSingleSlotInfo info = id.m_node->GetMultipleFunctionCallFromSingleSlotInfo(*id.m_slot);
if (info.functionCalls.empty())
{
return;
}
auto parent = execution->ModParent();
if (!parent)
{
AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), "Null parent in MultipleFunctionCall"));
return;
}
size_t indexInParentCall = parent->FindChildIndex(execution);
if (indexInParentCall >= parent->GetChildrenCount())
{
AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNoChildren));
return;
}
ExecutionChild* executionChildInParent = &parent->ModChild(indexInParentCall);
const size_t executionInputCount = execution->GetInputCount();
const size_t thisInputOffset = execution->InputHasThisPointer() ? 1 : 0;
// the original index has ALL the input from the slots on the node
// create multiple calls with separate function call nodes, but ONLY take the inputs required
// as indicated by the function call info
AZStd::unordered_set<const Slot*> usedSlots;
bool variadicIsFound = false;
auto createChild = [&](auto parentCall, ExecutionChild* childInParent, auto& functionCallInfo)
{
auto child = CreateChild(parentCall, id.m_node, id.m_slot);
child->SetSymbol(Symbol::FunctionCall);
child->SetName(functionCallInfo.functionName);
child->SetNameLexicalScope(functionCallInfo.lexicalScope);
childInParent->m_execution = child;
return child;
};
auto addThisInput = [&](auto functionCall)
{
if (thisInputOffset != 0)
{
if (executionInputCount == 0)
{
AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNotEnoughInputForThis));
return;
}
const ExecutionInput& input = execution->GetInput(0);
usedSlots.insert(input.m_slot);
functionCall->AddInput(input);
}
};
auto addSlotInput = [&](auto functionCall, size_t inputIndex)
{
if (inputIndex >= executionInputCount)
{
AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNotEnoughInput));
return;
}
const ExecutionInput& input = execution->GetInput(inputIndex);
if (usedSlots.contains(input.m_slot))
{
AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNotEnoughInput));
return;
}
usedSlots.insert(input.m_slot);
if (input.m_value->m_source == execution)
{
input.m_value->m_source = functionCall;
}
functionCall->AddInput(input);
};
auto addCall = [&](auto& functionCallInfo, auto childInParent, size_t startingIndex, size_t sentinel, size_t variadicOffset = 0)
{
auto child = createChild(parent, childInParent, functionCallInfo);
addThisInput(child);
for (size_t index = startingIndex; index < sentinel; ++index)
{
const size_t inputIndex = index + thisInputOffset + variadicOffset;
addSlotInput(child, inputIndex);
}
child->AddChild({});
childInParent = &child->ModChild(0);
return AZStd::make_pair(childInParent, child);
};
// loop through each call...
for (auto& functionCallInfo : info.functionCalls)
{
// ...first add any pre-variadic calls, using the starting index and the number of args, since they could come in any order, not input slot order...
if (!functionCallInfo.isVariadic)
{
AZStd::pair<ExecutionChild*, ExecutionTreePtr> childInParentAndParent = addCall(functionCallInfo, executionChildInParent, functionCallInfo.startingIndex, functionCallInfo.startingIndex + functionCallInfo.numArguments);
executionChildInParent = childInParentAndParent.first;
parent = childInParentAndParent.second;
}
else
{
// ...then add only one variadic call if there is one...
if (variadicIsFound)
{
AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotMultipleVariadic));
return;
}
variadicIsFound = true;
const size_t sentinel = executionInputCount == 0 ? 0 : executionInputCount - thisInputOffset;
// ... by looping through the remaining slots, striding by functionCallInfo.numArguments, making repeated calls to the function
for (size_t slotInputIndex = functionCallInfo.startingIndex; slotInputIndex < sentinel; slotInputIndex += functionCallInfo.numArguments)
{
AZStd::pair<ExecutionChild*, ExecutionTreePtr> childInParentAndParent = addCall(functionCallInfo, executionChildInParent, 0, functionCallInfo.numArguments, slotInputIndex);
executionChildInParent = childInParentAndParent.first;
parent = childInParentAndParent.second;
}
}
}
if (info.errorOnUnusedSlot && usedSlots.size() != executionInputCount)
{
AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotUnused));
}
// parent now refers to the last child call created
parent->SwapChildren(execution);
execution->Clear();
}
void AbstractCodeModel::ParseNodelingVariables(const Node& node, NodelingType nodelingType)
{
// #functions2 slot<->variable adjust once datums are more coordinated
auto createVariablesSlots = [&](AZStd::unordered_map<const Slot*, VariablePtr>& variablesBySlots, const AZStd::vector<const Slot*>& slots, bool slotHasDatum)
{
for (const auto& slot : slots)
{
auto variable = AZStd::make_shared<Variable>();
if (slotHasDatum)
{
auto variableDatum = slot->FindDatum();
if (!variableDatum)
{
AddError(nullptr, aznew Internal::ParseError(node.GetEntityId(), AZStd::string::format("Datum missing from Slot %s on Node %s", slot->GetName().data(), node.GetNodeName().c_str())));
return;
}
// #functions2 slot<->variable consider getting all variables from the UX variable manager, or from the ACM and looking them up in the variable manager for ordering
// auto iter = m_sourceVariableByDatum.find(variableDatum);
// if (iter == m_sourceVariableByDatum.end())
// {
// AddError(nullptr, aznew Internal::ParseError(node.GetEntityId(), AZStd::string::format("Datum missing from Slot %s on Node %s", slot->GetName().data(), node.GetNodeName().c_str())));
// return;
// }
// variable->m_sourceVariableId = iter->second->GetVariableId();
variable->m_datum = *variableDatum;
}
else
{
// make a new datum and a source slot id and all that
variable->m_datum.SetType(slot->GetDataType());
}
// Scope and name are initialized later
variable->m_sourceSlotId = slot->GetId();
variable->m_isFromFunctionDefinitionSlot = true;
variablesBySlots.insert({ slot, variable });
m_variables.push_back(variable);
}
};
if (nodelingType == NodelingType::In)
{
// get the output slots of the In-Nodeling
const auto inputs = node.GetSlotsByType(CombinedSlotType::DataOut);
createVariablesSlots(m_inputVariableByNodelingInSlot, inputs, false);
}
else if (nodelingType == NodelingType::Out)
{
// get the input slots of the Out-Nodeling
const auto outputs = node.GetSlotsByType(CombinedSlotType::DataIn);
createVariablesSlots(m_outputVariableByNodelingOutSlot, outputs, true);
}
else if (nodelingType == NodelingType::OutReturn)
{
// get the output slots of the Out-Nodeling
const auto inputs = node.GetSlotsByType(CombinedSlotType::DataOut);
createVariablesSlots(m_returnVariableByNodelingOutSlot, inputs, false);
}
}
void AbstractCodeModel::ParseOperatorArithmetic(ExecutionTreePtr execution)
{
const CheckOperatorResult result = CheckOperatorArithmeticSymbol(execution);
execution->SetSymbol(result.symbol);
if (!result.name.empty())
{
execution->SetName(result.name);
execution->SetNameLexicalScope(result.lexicalScope);
}
if (execution->GetSymbol() == Symbol::Count)
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), ParseErrors::UntranslatedArithmetic));
}
else if (IsOperatorArithmetic(execution))
{
// \todo check input validity, including for compile time division by zero
if (execution->GetInputCount() < 2)
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), ParseErrors::NotEnoughArgsForArithmeticOperator));
}
}
}
void AbstractCodeModel::ParseOutputData(ExecutionTreePtr execution, ExecutionChild& executionChild)
{
if (const auto nodeling = azrtti_cast<const Nodes::Core::FunctionDefinitionNode*>(execution->GetId().m_node))
{
// this nodeling will always be the Execution-In part of the function defintion
// since a call to a user out does not enter this path
AZ_Assert(execution->GetSymbol() != Symbol::UserOut, "User Out data should not be processed here");
ParseUserInData(execution, executionChild);
return;
}
if (auto writtenVariable = GetWrittenVariable(execution))
{
executionChild.m_output.push_back({ execution->GetId().m_node->GetVariableOutputSlot(), CreateOutputAssignment(writtenVariable) });
}
// this can never called on a branch
auto outputSlotsOutcome = ParseDataOutSlots(execution, executionChild);
if (outputSlotsOutcome.IsSuccess())
{
ParseOutputData(execution, executionChild, outputSlotsOutcome.GetValue());
if (execution->GetSymbol() == Symbol::FunctionDefinition)
{
auto returnSlotsOutcome = execution->GetId().m_node->GetSlotsInExecutionThreadByType(*(execution->GetId().m_slot), CombinedSlotType::DataIn);
if (returnSlotsOutcome.IsSuccess())
{
for (auto outputSlot : returnSlotsOutcome.GetValue())
{
ParseReturnValue(execution, *outputSlot);
}
}
else
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), returnSlotsOutcome.TakeError()));
}
}
}
else
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), outputSlotsOutcome.TakeError()));
}
}
void AbstractCodeModel::ParseOutputData(ExecutionTreePtr execution, ExecutionChild& executionChild, AZStd::vector<const Slot*>& slots)
{
for (auto outputSlot : slots)
{
ParseOutputData(execution, executionChild, *outputSlot);
}
}
void AbstractCodeModel::ParseOutputData(ExecutionTreePtr execution, ExecutionChild& executionChild, const Slot& output)
{
if (auto newOutput = CreateOutputData(execution, executionChild, output))
{
executionChild.m_output.push_back({ &output, newOutput });
}
}
void AbstractCodeModel::ParsePropertyExtractionsPost(ExecutionTreePtr execution)
{
if (execution->GetSymbol() == Symbol::FunctionDefinition)
{
return;
}
// every property extraction has to be individually processed, each one is made into a its one node in the execution tree
ExecutionTreePtr parent = execution;
const auto& propertyExtractionSources = execution->GetPropertyExtractionSources();
if (!propertyExtractionSources.empty() && execution->GetChildrenCount() == 0)
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), ParseErrors::NoChildrenInExtraction));
return;
}
for (auto propertyExtractionIter : propertyExtractionSources)
{
if (auto propertyOutput = RemoveOutput(execution->ModChild(0), propertyExtractionIter.first->GetId()))
{
if (propertyExtractionIter.second)
{
ExecutionTreePtr extraction = CreateChild(execution, execution->GetId().m_node, execution->GetId().m_slot);
if (auto writtenVariable = GetWrittenVariable(execution))
{
// nullptr is acceptable here
extraction->AddInput({ nullptr, writtenVariable, DebugDataSource::FromVariable(SlotId{}, writtenVariable->m_datum.GetType(), writtenVariable->m_sourceVariableId) });
}
else
{
extraction->CopyInput(execution, ExecutionTree::RemapVariableSource::No);
}
extraction->SetExecutedPropertyExtraction(propertyExtractionIter.second);
// make sure the correct node is responsible for creating the output
if (propertyOutput->m_source->m_source == execution)
{
propertyOutput->m_source->m_source = extraction;
}
// the child output is only the property extraction
ExecutionChild child;
child.m_output.push_back({ propertyExtractionIter.first, propertyOutput });
extraction->AddChild(child);
// insert the extraction into the tree
extraction->SetParent(parent);
extraction->ModChild(0).m_execution = parent->GetChild(0).m_execution;
parent->ModChild(0).m_execution = extraction;
parent = extraction;
}
}
else
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), ParseErrors::NoOutForExecution));
}
}
execution->ClearProperyExtractionSources();
}
void AbstractCodeModel::ParsePropertyExtractionsPre(ExecutionTreePtr execution)
{
if (execution->GetSymbol() == Symbol::FunctionDefinition)
{
return;
}
auto propertyFields = execution->GetId().m_node->GetPropertyFields();
for (auto propertyField : propertyFields)
{
const auto slot = execution->GetId().m_node->GetSlot(propertyField.second);
AZ_Assert(slot, "not slot by name %s", propertyField.first.data());
if (slot->IsVariableReference() || !execution->GetId().m_node->GetConnectedNodes(*slot).empty())
{
PropertyExtractionPtr extraction = AZStd::make_shared<PropertyExtraction>();
extraction->m_slot = slot;
extraction->m_name = propertyField.first;
execution->AddPropertyExtractionSource(slot, extraction);
}
else
{
execution->AddPropertyExtractionSource(slot, nullptr);
}
}
}
void AbstractCodeModel::ParseReturnValue(ExecutionTreePtr execution, const Slot& returnValueSlot)
{
if (auto variable = FindReferencedVariableChecked(execution, returnValueSlot))
{
ParseReturnValue(execution, variable, &returnValueSlot);
}
else
{
auto returnValueOutput = CreateOutput(execution, returnValueSlot, {}, "return");
ReturnValuePtr returnValue = AZStd::make_shared<ReturnValue>(AZStd::move(*returnValueOutput.get()));
auto nodes = execution->GetId().m_node->GetConnectedNodes(returnValueSlot);
if (!nodes.empty())
{
if (auto sourceVariable = ParseConnectedInputData(returnValueSlot, execution, nodes, FirstNode::Self))
{
returnValue->m_initializationValue = sourceVariable;
returnValue->m_sourceDebug = DebugDataSource::FromReturn(returnValueSlot, execution, sourceVariable);
}
}
else
{
returnValue->m_sourceDebug = DebugDataSource::FromSelfSlot(returnValueSlot);
}
execution->AddReturnValue(&returnValueSlot, returnValue);
}
}
void AbstractCodeModel::ParseReturnValue(ExecutionTreePtr execution, VariableConstPtr variable, const Slot* returnValueSlot)
{
auto returnValueOutput = CreateOutputAssignment(variable);
ReturnValuePtr returnValue = AZStd::make_shared<ReturnValue>(AZStd::move(*returnValueOutput.get()));
returnValue->m_isNewValue = !variable->m_isMember;
// this will need a refactor in terms of debug info for function graphs
if (returnValueSlot)
{
returnValue->m_sourceDebug = DebugDataSource::FromReturn(*returnValueSlot, execution, variable);
}
execution->AddReturnValue(nullptr, returnValue);
}
void AbstractCodeModel::ParseUserFunctionTopology()
{
for (auto& iter : m_userInsThatRequireTopology)
{
ParseUserIn(iter.second, iter.first);
}
m_userInsThatRequireTopology.clear();
ParseUserOuts();
auto parseOutcome = m_subgraphInterface.Parse();
if (!parseOutcome.IsSuccess())
{
AddError(nullptr, aznew Internal::ParseError(AZ::EntityId(), AZStd::string::format("Subgraph interface failed to parse: %s", parseOutcome.GetError().c_str()).c_str()));
}
}
void AbstractCodeModel::ParseUserIn(ExecutionTreePtr root, const Nodes::Core::FunctionDefinitionNode* nodeling)
{
// make sure this name is unique
size_t defaultAdded = 0;
// get all the nodelings, all the leaves, and the calls to nodelings out
NodelingInParserIterationListener listener;
TraverseTree(root, listener);
auto& leavesWithoutNodelings = listener.GetLeavesWithoutNodelings();
AZStd::unordered_set<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*> uniqueNodelingsOut = listener.GetNodelingsOut();
// determine the the execution topology can be reduced to single function call with a single return point to graph execution
const UserInParseTopologyResult result = ParseUserInTolopology(uniqueNodelingsOut.size(), leavesWithoutNodelings.size());
// determine the name of the default out if one needs to be added to leaf nodes with with no execution out calls
AZStd::string defaultOutNameCandidate = CheckUniqueOutNames(root->GetName(), uniqueNodelingsOut);
AZStd::string defaultOutName;
if (result.addSingleOutToMap)
{
// force all names to be unique, make sure the new name is unique, use result of topology query for name
// try for in name, if that doesn't work, just make a new one based on, "Out"
defaultOutName = defaultOutNameCandidate;
defaultAdded = 1;
}
if (result.addNewOutToLeavesWithout)
{
for (auto& leafWithout : leavesWithoutNodelings)
{
AddUserOutToLeaf(AZStd::const_pointer_cast<ExecutionTree>(leafWithout), root, defaultOutName);
}
}
// this is a sanity check now to verify there are no leaves
NodelingInParserIterationListener listenerCheck;
listenerCheck.CountOnlyGrammarCalls();
TraverseTree(root, listenerCheck);
if (result.addNewOutToLeavesWithout)
{
auto& leavesWithoutNodelingsChecked = listenerCheck.GetLeavesWithoutNodelings();
if (!leavesWithoutNodelingsChecked.empty())
{
AddError(root, aznew Internal::ParseError(AZ::EntityId(), "In Nodeling didn't parse properly, there were still leaves without nodelings in the execution tree."));
return;
}
}
auto& outCallsChecked = listenerCheck.GetOutCalls();
if (!result.addSingleOutToMap && outCallsChecked.empty())
{
AddError(root, aznew Internal::ParseError(AZ::EntityId(), "In Nodeling didn't parse properly, the parser failed to generate an immediate out."));
return;
}
const size_t branches = uniqueNodelingsOut.size() + defaultAdded;
if (result.addExplicitOutCalls)
{
if (branches < 2)
{
AddError(root, aznew Internal::ParseError(AZ::EntityId(), "In Nodeling didn't parse properly, attempting explicit Outs without user defined branches"));
return;
}
root->MarkHasExplicitUserOutCalls();
for (auto& outCall : outCallsChecked)
{
AZStd::const_pointer_cast<ExecutionTree>(outCall)->CopyReturnValuesToInputs(root);
}
}
else if (branches > 1)
{
AddError(root, aznew Internal::ParseError(AZ::EntityId(), "In Nodeling didn't parse properly, attempting default return even with branches"));
return;
}
if (branches <= 1)
{
for (auto outCallChecked : outCallsChecked)
{
auto nodelingOut = AZStd::const_pointer_cast<ExecutionTree>(outCallChecked);
nodelingOut->SetSymbol(Symbol::PlaceHolderDuringParsing);
nodelingOut->MarkDebugEmptyStatement();
}
}
if (!defaultOutName.empty() && result.addSingleOutToMap)
{
uniqueNodelingsOut.insert(nullptr);
}
if ((!root->HasExplicitUserOutCalls())
&& root->GetReturnValueCount() > 0
&& branches > 1)
{
AddError(root->GetNodeId(), root, ScriptCanvas::ParseErrors::TooManyBranchesForReturn);
return;
}
// ALWAYS MAKE A MAP, send it to the output, regardless
AddExecutionMapIn(result, root, outCallsChecked, defaultOutName, nodeling, uniqueNodelingsOut);
}
void AbstractCodeModel::ParseUserInData(ExecutionTreePtr execution, ExecutionChild& executionChild)
{
if (execution->IsOnLatentPath())
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), "latent execution parsed data in immediate thread"));
}
else
{
// inputs to the user function
const auto input = FindUserImmediateInput(execution);
for (auto& inputValue : input)
{
auto outputAssignment = CreateOutputAssignment(inputValue);
inputValue->m_source = execution;
executionChild.m_output.push_back({ nullptr, outputAssignment });
}
// (all possible, even variant) output from the function
if (const auto output = FindUserImmediateOutput(execution))
{
for (auto& outputValue : output->returnValues)
{
ParseReturnValue(execution, outputValue, nullptr);
AZStd::pair<const Slot*, ReturnValueConstPtr> returnValue = execution->GetReturnValue(execution->GetReturnValueCount() - 1);
AZStd::const_pointer_cast<ReturnValue>(returnValue.second)->m_isNewValue = true;
}
}
}
}
AbstractCodeModel::UserInParseTopologyResult AbstractCodeModel::ParseUserInTolopology(size_t nodelingsOutCount, size_t leavesWithoutNodelingsCount)
{
AbstractCodeModel::UserInParseTopologyResult result;
if (nodelingsOutCount == 0)
{
// easy definition: syntax sugar where we give the user an Out for free
result.addSingleOutToMap = true;
result.addNewOutToLeavesWithout = false;
result.addExplicitOutCalls = false;
result.isSimpleFunction = true;
}
else if (leavesWithoutNodelingsCount == 0)
{
// user defined every possible out
result.addSingleOutToMap = false;
result.addNewOutToLeavesWithout = false;
result.addExplicitOutCalls = nodelingsOutCount > 1;
result.isSimpleFunction = !result.addExplicitOutCalls;
}
else
{
// user explicitly defined at least 1 Out and there are execution leaves without Outs, so we provide an Out to any missing ones
result.addSingleOutToMap = true;
result.addNewOutToLeavesWithout = true;
result.addExplicitOutCalls = true;
result.isSimpleFunction = false;
}
return result;
}
void AbstractCodeModel::ParseUserLatent(ExecutionTreePtr call, const Nodes::Core::FunctionDefinitionNode* nodeling)
{
if (call && !call->GetRoot())
{
AddError(call, aznew Internal::ParseError(call->GetNodeId(), "User Out call has no parent"));
return;
}
// process data values into the proper call
ParseUserLatentData(call); // <- do this for very ACM node
AddExecutionMapLatentOut(*nodeling, call); // <- do this once for the nodeling
call->MarkHasExplicitUserOutCalls();
call->ModRoot()->MarkHasExplicitUserOutCalls();
}
void AbstractCodeModel::ParseUserLatentData(ExecutionTreePtr execution)
{
if (execution->IsOnLatentPath())
{
if (execution->GetChildrenCount() == 0)
{
execution->AddChild({ nullptr, {}, nullptr });
}
auto& executionChild = execution->ModChild(0);
// inputs are return values expected from the latent out call
for (auto& returnValue : FindUserLatentReturnValues(execution))
{
// if there are return values, we can continue execution after
// the nodeling out that is in the path (disable the contract)
// and we must make sure there's ONLY ONE
// and no immediate ins
auto outputAssignment = CreateOutputAssignment(returnValue);
executionChild.m_output.push_back({ nullptr, outputAssignment });
}
auto methodRoot = execution->ModRoot();
// outputs are inputs to the latent out call
for (auto& inputValue : FindUserLatentOutput(execution))
{
inputValue->m_source = methodRoot;
execution->AddInput({ nullptr, inputValue, DebugDataSource::FromVariable(SlotId{}, inputValue->m_datum.GetType(), inputValue->m_sourceVariableId) });
}
methodRoot->CopyInput(execution, ExecutionTree::RemapVariableSource::No);
}
else
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), "immediate execution parsed data in latent thread"));
}
}
void AbstractCodeModel::ParseUserOutCall(ExecutionTreePtr execution)
{
if (execution && !execution->GetRoot())
{
AddError(execution, aznew Internal::ParseError(execution->GetNodeId(), "User Out call has no parent"));
return;
}
if (IsInLoop(execution))
{
AddError(execution->GetId().m_node->GetEntityId(), execution, ScriptCanvas::ParseErrors::UserOutCallInLoop);
return;
}
if (IsMidSequence(execution))
{
AddError(execution->GetId().m_node->GetEntityId(), execution, ScriptCanvas::ParseErrors::UserOutCallMidSequence);
return;
}
execution->SetSymbol(Symbol::UserOut);
auto nodeling = azrtti_cast<const Nodes::Core::FunctionDefinitionNode*>(execution->GetId().m_node);
execution->SetName(nodeling->GetDisplayName());
if (execution->IsOnLatentPath())
{
// Data for these calls processed later here: ParseUserOuts
m_outsMarkedLatent.insert({ nodeling, execution });
}
else
{
// Data for these calls are processed later here: ParseUserInData
// since user out calls indicate branches in function definitions, their data is processed when
// the function is defined, since the return values could be modified at any time starting from the beginning of the function
m_outsMarkedImmediate.insert(nodeling);
}
}
void AbstractCodeModel::ParseUserOuts()
{
using namespace AbstractCodeModelCpp;
const AZStd::unordered_set< const Nodes::Core::FunctionDefinitionNode*> intersection = Intersection(m_outsMarkedLatent, m_outsMarkedImmediate);
if (!intersection.empty())
{
AZStd::string report("User out(s) used in both immediate and latent out paths, immediate and latent outs cannot be shared");
bool isFirst = true;
AZ::EntityId nodeId;
for (auto doubleOut : intersection)
{
report += isFirst ? ": " : ", ";
report += doubleOut->GetDisplayName();
isFirst = false;
}
// todo may need to send multiple node Ids
AddError(nullptr, aznew Internal::ParseError(nodeId, report));
return;
}
for (auto nodeling : m_outsMarkedImmediate)
{
if (nodeling)
{
if (!IsConnectedToUserIn(nodeling))
{
const auto report = AZStd::string::format
( "Nodeling Out (%s) not connected to Nodeling In, functionality cannot be executed", nodeling->GetDisplayName().data());
AddError(nullptr, aznew Internal::ParseError(nodeling->GetEntityId(), report));
}
}
else
{
const auto report = AZStd::string::format
("null nodeling in immediate out list");
AddError(nullptr, aznew Internal::ParseError(nodeling->GetEntityId(), report));
}
}
for (auto userLatentOut : m_outsMarkedLatent)
{
ParseUserLatent(userLatentOut.second, userLatentOut.first);
}
}
void AbstractCodeModel::ParseVariableHandling()
{
auto isNotActive = [&](auto iter)->bool
{
if (!Parse(iter.second))
{
iter.second->Clear();
return true;
}
else
{
return false;
}
};
AZStd::erase_if(m_variableWriteHandlingBySlot, isNotActive);
}
bool AbstractCodeModel::ParseVariableUseAndPurity(ExecutionTreePtr execution)
{
// gather a list of all the variables used in the function scope
// and in the graph scope in this execution.
PureFunctionListener listener;
TraverseTree(execution, listener);
const auto& usage = listener.GetUsedVariables();
const bool usesOnlyLocalVariables = usage.memberVariables.empty() && usage.implicitMemberVariables.empty();
m_variableUse.localVariables.insert(usage.localVariables.begin(), usage.localVariables.end());
m_variableUse.memberVariables.insert(usage.memberVariables.begin(), usage.memberVariables.end());
for (auto variable : m_variableUse.localVariables)
{
if (const AZStd::pair<VariableConstPtr, AZStd::string>* pair = FindStaticVariable(variable))
{
auto& localStatics = ModStaticVariablesNames(execution);
auto iter = AZStd::find_if
(localStatics.begin()
, localStatics.end()
, [&](const auto& candidate) { return candidate.first == variable; });
if (iter == localStatics.end())
{
localStatics.push_back(*pair);
}
}
}
m_variableUseByExecution.emplace(execution, listener.MoveUsedVariables());
return (!usage.usesExternallyInitializedVariables) && usesOnlyLocalVariables && listener.IsPure();
}
void AbstractCodeModel::PostParseErrorDetect(ExecutionTreePtr root)
{
if (IsInfiniteSelfEntityActivationLoop(*this, root))
{
AddError(root->GetId().m_node->GetEntityId(), root, ScriptCanvas::ParseErrors::InfiniteSelfActivationLoop);
}
if (HasPostSelfDeactivationActivity(*this, root))
{
AddError(root->GetId().m_node->GetEntityId(), root, ScriptCanvas::ParseErrors::ExecutionAfterSelfDeactivation);
}
}
void AbstractCodeModel::PostParseProcess(ExecutionTreePtr root)
{
PruneNoOpChildren(root);
ParseEntityIdInput(root);
}
void AbstractCodeModel::PruneNoOpChildren(const ExecutionTreePtr& execution)
{
AZStd::vector<ExecutionTreePtr> noOpChildren;
for (size_t index(0); index < execution->GetChildrenCount(); ++index)
{
auto& child = execution->ModChild(index);
if (child.m_execution)
{
PruneNoOpChildren(child.m_execution);
if (IsNoOp(*this, execution, child))
{
if (child.m_output.empty())
{
noOpChildren.push_back(child.m_execution);
}
else
{
child.m_execution->SetSymbol(Symbol::DebugInfoEmptyStatement);
}
}
}
}
for (auto noOpChild : noOpChildren)
{
RemoveFromTree(noOpChild);
}
}
void AbstractCodeModel::RemoveFromTree(ExecutionTreePtr execution)
{
if (!execution->GetParent())
{
AddError(execution->GetNodeId(), execution, ScriptCanvas::ParseErrors::MissingParentOfRemovedNode);
}
auto removeChildOutcome = RemoveChild(execution->ModParent(), execution);
if (removeChildOutcome.IsSuccess())
{
auto childCount = execution->GetChildrenCount();
auto indexAndChild = removeChildOutcome.TakeValue();
ExecutionChild& removedChild = indexAndChild.second;
if (!removedChild.m_output.empty() && childCount == 0)
{
AddError(execution->GetNodeId(), execution, ScriptCanvas::ParseErrors::RequiredOutputRemoved);
}
if (childCount != 0)
{
if (childCount > 1)
{
AddError(execution->GetNodeId(), execution, ScriptCanvas::ParseErrors::CannotRemoveMoreThanOneChild);
}
auto& child = execution->ModChild(0);
child.m_slot = removedChild.m_slot;
child.m_output = removedChild.m_output;
execution->ModParent()->InsertChild(indexAndChild.first, child);
if (child.m_execution)
{
child.m_execution->SetParent(execution->ModParent());
}
execution->ClearChildren();
}
}
else
{
AddError(execution->GetNodeId(), execution, ScriptCanvas::ParseErrors::FailedToRemoveChild);
}
execution->Clear();
}
AZ::Outcome<AZStd::pair<size_t, ExecutionChild>> AbstractCodeModel::RemoveChild(const ExecutionTreePtr& execution, const ExecutionTreeConstPtr& child)
{
return execution->RemoveChild(child);
}
bool AbstractCodeModel::RequiresCreationFunction(Data::eType type)
{
return type == Data::eType::BehaviorContextObject;
}
}
}