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/ParsingUtilities.cpp

1455 lines
53 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/RTTI/BehaviorContextUtilities.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <ScriptCanvas/Core/Graph.h>
#include <ScriptCanvas/Core/Node.h>
#include <ScriptCanvas/Core/Slot.h>
#include <ScriptCanvas/Execution/ExecutionState.h>
#include <ScriptCanvas/Libraries/Comparison/Comparison.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/GetVariable.h>
#include <ScriptCanvas/Libraries/Core/Method.h>
#include <ScriptCanvas/Libraries/Core/ReceiveScriptEvent.h>
#include <ScriptCanvas/Libraries/Core/SetVariable.h>
#include <ScriptCanvas/Libraries/Core/Start.h>
#include <ScriptCanvas/Libraries/Logic/Break.h>
#include <ScriptCanvas/Libraries/Logic/Cycle.h>
#include <ScriptCanvas/Libraries/Logic/IsNull.h>
#include <ScriptCanvas/Libraries/Logic/Once.h>
#include <ScriptCanvas/Libraries/Logic/OrderedSequencer.h>
#include <ScriptCanvas/Libraries/Logic/WeightedRandomSequencer.h>
#include <ScriptCanvas/Libraries/Logic/While.h>
#include <ScriptCanvas/Libraries/Math/MathExpression.h>
#include <ScriptCanvas/Libraries/Operators/Math/OperatorAdd.h>
#include <ScriptCanvas/Libraries/Operators/Math/OperatorArithmetic.h>
#include <ScriptCanvas/Libraries/Operators/Math/OperatorDiv.h>
#include <ScriptCanvas/Libraries/Operators/Math/OperatorMul.h>
#include <ScriptCanvas/Libraries/Operators/Math/OperatorSub.h>
#include <ScriptCanvas/Translation/Configuration.h>
#include <ScriptCanvas/Variable/VariableData.h>
#include "AbstractCodeModel.h"
#include "ParsingUtilities.h"
namespace ParsingUtilitiesCpp
{
using namespace ScriptCanvas;
using namespace ScriptCanvas::Grammar;
class PrettyPrinter
: public ExecutionTreeTraversalListener
{
public:
PrettyPrinter() = default;
PrettyPrinter(ExecutionTreeConstPtr marker)
: m_marker(marker)
{
}
void Evaluate(ExecutionTreeConstPtr execution, const Slot* slot, int level)
{
for (int i = 0; i < level; ++i)
{
m_result += "\t";
}
if (slot)
{
m_result += slot->GetName();
m_result += ":";
}
AZStd::string executionNodeName = execution->GetName();
m_result += executionNodeName.empty() ? execution->GetId().m_node ? execution->GetId().m_node->GetNodeName() : executionNodeName : executionNodeName;
m_result += ":";
m_result += execution->GetId().m_slot ? execution->GetId().m_slot->GetName() : "<>";
m_result += "[";
m_result += GetSymbolName(execution->GetSymbol());
m_result += "]";
size_t childCount = execution->GetChildrenCount();
if (childCount != 0)
{
m_result += AZStd::string::format(" # children: %zu", childCount);
}
if (m_marker == execution)
{
m_result += " <<<< MARKER <<<< ";
}
}
void EvaluateChildPre(ExecutionTreeConstPtr, const Slot*, size_t, int)
{
m_result += "\n";
}
void EvaluateRoot(ExecutionTreeConstPtr node, const Slot* )
{
m_result += "\nRoot:\n";
}
const AZStd::string& GetResult() const
{
return m_result;
}
AZStd::string&& TakeResult()
{
return AZStd::move(m_result);
}
private:
AZStd::string m_result;
ExecutionTreeConstPtr m_marker;
};
bool IsBehaviorContextProperty(const Node* node, const Slot* slot, bool checkRead, bool checkWrite)
{
auto methodNode = azrtti_cast<const Nodes::Core::Method*>(node);
if (!methodNode)
{
return false;
}
auto behaviorContext = AZ::GetDefaultBehaviorContext();
if (!behaviorContext)
{
return false;
}
auto nameOutcome = methodNode->GetFunctionCallName(slot);
if (!nameOutcome.IsSuccess())
{
return false;
}
AZStd::string sanitized(nameOutcome.GetValue());
AZ::RemovePropertyNameArtifacts(sanitized);
auto iter = behaviorContext->m_properties.find(sanitized);
if (iter == behaviorContext->m_properties.end())
{
return false;
}
if (checkRead && !iter->second->m_getter)
{
return false;
}
if (checkWrite && !iter->second->m_setter)
{
return false;
}
return true;
}
bool IsBehaviorContextPropertyRead(const Node* node, const Slot* slot)
{
return IsBehaviorContextProperty(node, slot, true, false);
}
bool IsBehaviorContextPropertyWrite(const Node* node, const Slot* slot)
{
return IsBehaviorContextProperty(node, slot, false, true);
}
}
namespace ScriptCanvas
{
namespace Grammar
{
CheckOperatorResult::CheckOperatorResult(Symbol symbol)
: symbol(symbol)
{}
CheckOperatorResult::CheckOperatorResult(Symbol symbol, AZStd::string_view name, LexicalScopeType scopeType)
: symbol(symbol)
, name(name)
, lexicalScope(scopeType)
{}
void VariableUseage::Clear()
{
localVariables.clear();
memberVariables.clear();
}
void VariableUseage::Parse(VariableConstPtr variable)
{
usesExternallyInitializedVariables = usesExternallyInitializedVariables || IsExternallyInitialized(variable);
if (IsManuallyDeclaredUserVariable(variable))
{
if (variable->m_isMember)
{
memberVariables.insert(variable);
}
else
{
localVariables.insert(variable);
}
}
else if (variable->m_isMember)
{
implicitMemberVariables.insert(variable);
}
}
bool ActivatesSelf(const ExecutionTreeConstPtr& execution)
{
auto methodNode = azrtti_cast<const ScriptCanvas::Nodes::Core::Method*>(execution->GetId().m_node);
if (!methodNode)
{
return false;
}
auto bcMethod = methodNode->GetMethod();
if (!bcMethod)
{
return false;
}
if (bcMethod->m_name != "ActivateGameEntity")
{
return false;
}
if (execution->GetInputCount() != 1)
{
return false;
}
if (execution->GetInput(0).m_slot && execution->GetInput(0).m_slot->IsConnected())
{
return false;
}
const auto& addressDatum = execution->GetInput(0).m_value->m_datum;
Data::EntityIDType entityAddress;
if (auto entityId = addressDatum.GetAs<Data::EntityIDType>())
{
entityAddress = *entityId;
}
else if (auto namedEntityId = addressDatum.GetAs<Data::NamedEntityIDType>())
{
entityAddress = *namedEntityId;
}
if (entityAddress != GraphOwnerId)
{
return false;
}
return true;
}
EventHandingType CheckEventHandlingType(const ExecutionTreeConstPtr& execution)
{
return execution->GetId().m_node ? CheckEventHandlingType(*execution->GetId().m_node) : EventHandingType::Count;
}
EventHandingType CheckEventHandlingType(const Node& node)
{
if (azrtti_istypeof<const ScriptCanvas::Nodes::Core::EBusEventHandler*>(&node))
{
return node.IsVariableWriteHandler() ? EventHandingType::VariableWrite : EventHandingType::EBus;
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::Core::ReceiveScriptEvent*>(&node))
{
return EventHandingType::EBus;
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::Core::AzEventHandler*>(&node))
{
return EventHandingType::Event;
}
else
{
return EventHandingType::Count;
}
}
Symbol CheckLogicalExpressionSymbol(const ExecutionTreeConstPtr& execution)
{
if (IsIsNull(execution))
{
return Symbol::IsNull;
}
else if (execution->GetId().m_node->IsLogicalAND())
{
return Symbol::LogicalAND;
}
else if (execution->GetId().m_node->IsLogicalNOT())
{
return Symbol::LogicalNOT;
}
else if (execution->GetId().m_node->IsLogicalOR())
{
return Symbol::LogicalOR;
}
if (azrtti_istypeof<const ScriptCanvas::Nodes::ComparisonExpression*>(execution->GetId().m_node))
{
if (azrtti_istypeof<const ScriptCanvas::Nodes::Comparison::Greater*>(execution->GetId().m_node))
{
return Symbol::CompareGreater;
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::Comparison::GreaterEqual*>(execution->GetId().m_node))
{
return Symbol::CompareGreaterEqual;
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::Comparison::Less*>(execution->GetId().m_node))
{
return Symbol::CompareLess;
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::Comparison::LessEqual*>(execution->GetId().m_node))
{
return Symbol::CompareLessEqual;
}
else
{
return Symbol::Count;
}
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::EqualityExpression*>(execution->GetId().m_node))
{
if (azrtti_istypeof<const ScriptCanvas::Nodes::Comparison::EqualTo*>(execution->GetId().m_node))
{
return Symbol::CompareEqual;
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::Comparison::NotEqualTo*>(execution->GetId().m_node))
{
return Symbol::CompareNotEqual;
}
else
{
return Symbol::Count;
}
}
return execution->GetSymbol();
}
NodelingType CheckNodelingType(const Node& node)
{
if (auto nodeling = azrtti_cast<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>(&node))
{
if (nodeling->IsExecutionEntry())
{
return NodelingType::In;
}
else if (nodeling->IsExecutionExit())
{
return NodelingType::Out;
}
}
return NodelingType::None;
}
CheckOperatorResult CheckOperatorArithmeticSymbol(const ExecutionTreeConstPtr& execution)
{
if (azrtti_istypeof<const ScriptCanvas::Nodes::Operators::OperatorArithmetic*>(execution->GetId().m_node))
{
if (azrtti_istypeof<const ScriptCanvas::Nodes::Operators::OperatorAdd*>(execution->GetId().m_node))
{
if (execution->GetInputCount() > 0 && execution->GetInput(0).m_value->m_datum.GetType() == Data::Type::Color())
{
return { Symbol::FunctionCall, "AddClamped", LexicalScopeType::Variable };
}
else
{
return Symbol::OperatorAddition;
}
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::Operators::OperatorDiv*>(execution->GetId().m_node))
{
return Symbol::OperatorDivision;
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::Operators::OperatorMul*>(execution->GetId().m_node))
{
return Symbol::OperatorMultiplication;
}
else if (azrtti_istypeof<const ScriptCanvas::Nodes::Operators::OperatorSub*>(execution->GetId().m_node))
{
if (execution->GetInputCount() > 0 && execution->GetInput(0).m_value->m_datum.GetType() == Data::Type::Color())
{
return { Symbol::FunctionCall, "SubtractClamped", LexicalScopeType::Variable };
}
else
{
return Symbol::OperatorSubraction;
}
}
else
{
return Symbol::Count;
}
}
else if (auto methodNode = azrtti_cast<const ScriptCanvas::Nodes::Core::Method*>(execution->GetId().m_node))
{
if (auto method = methodNode->GetMethod())
{
AZ::Script::Attributes::OperatorType operatorType;
if (AZ::ReadAttribute(operatorType, AZ::ScriptCanvasAttributes::OperatorOverride, method->m_attributes))
{
switch (operatorType)
{
case AZ::Script::Attributes::OperatorType::Add:
return Symbol::OperatorAddition;
case AZ::Script::Attributes::OperatorType::Sub:
return Symbol::OperatorSubraction;
case AZ::Script::Attributes::OperatorType::Mul:
return Symbol::OperatorMultiplication;
case AZ::Script::Attributes::OperatorType::Div:
return Symbol::OperatorDivision;
default:
break;
}
}
}
}
return execution->GetSymbol();
}
void DenumberFirstCharacter(AZStd::string& identifier)
{
if (!identifier.empty() && isdigit(identifier.at(0)))
{
identifier.insert(0, "_");
}
}
bool ExecutionContainsCycles(const Node& node, const Slot& outSlot)
{
GraphExecutionPathTraversalListener listener;
return TraverseExecutionConnections(node, outSlot, listener) != ExecutionTraversalResult::Success;
}
bool ExecutionWritesVariable(ExecutionTreeConstPtr execution, VariableConstPtr variable)
{
for (size_t i = 0, sentinel = execution->GetChildrenCount(); i < sentinel; ++i)
{
auto& child = execution->GetChild(i);
for (auto& output : child.m_output)
{
if (output.second->m_source == variable)
{
return true;
}
for (auto& assignment : output.second->m_assignments)
{
if (assignment == variable)
{
return true;
}
}
}
}
return false;
}
const Slot* GetOnceOnResetSlot(const Node& node)
{
return OnceProperty::GetOnResetSlot(&node);
}
const Slot* GetOnceOutSlot(const Node& node)
{
return OnceProperty::GetOutSlot(&node);
}
bool HasPostSelfDeactivationActivity(const AbstractCodeModel& model, ExecutionTreeConstPtr execution)
{
bool isSelfDeactivationFound = false;
return HasPostSelfDeactivationActivityRecurse(model, execution, isSelfDeactivationFound);
}
bool HasPostSelfDeactivationActivityRecurse(const AbstractCodeModel& model, ExecutionTreeConstPtr execution, bool& isSelfDeactivationFound)
{
isSelfDeactivationFound = isSelfDeactivationFound || IsDetectableSelfDeactivation(execution);
for (size_t index = 0; index < execution->GetChildrenCount(); ++index)
{
auto& child = execution->GetChild(index);
if (child.m_execution)
{
if (!IsNoOp(model, execution, child) && isSelfDeactivationFound)
{
return true;
}
if (HasPostSelfDeactivationActivityRecurse(model, child.m_execution, isSelfDeactivationFound))
{
return true;
}
}
}
return false;
}
bool HasPropertyExtractions(const ExecutionTreeConstPtr& execution)
{
return execution->GetPropertyExtractionSources().empty();
}
bool HasReturnValues(const ExecutionTreeConstPtr& execution)
{
return execution->HasReturnValues();
}
bool IsBreak(const ExecutionTreeConstPtr& execution)
{
if (auto forEach = azrtti_cast<const ScriptCanvas::Nodes::Core::ForEach*>(execution->GetId().m_node))
{
if (forEach->GetLoopBreakSlotId() == execution->GetId().m_slot->GetId())
{
return true;
}
}
return azrtti_istypeof<const ScriptCanvas::Nodes::Logic::Break*>(execution->GetId().m_node);
}
bool IsClassPropertyRead(ExecutionTreeConstPtr execution)
{
return execution->GetSymbol() == Symbol::FunctionCall
&& azrtti_istypeof<const ScriptCanvas::Nodes::Core::Method*>(execution->GetId().m_node)
&& azrtti_cast<const ScriptCanvas::Nodes::Core::Method*>(execution->GetId().m_node)->GetPropertyStatus() == PropertyStatus::Getter;
}
bool IsClassPropertyWrite(ExecutionTreeConstPtr execution)
{
return execution->GetSymbol() == Symbol::FunctionCall
&& azrtti_istypeof<const ScriptCanvas::Nodes::Core::Method*>(execution->GetId().m_node)
&& azrtti_cast<const ScriptCanvas::Nodes::Core::Method*>(execution->GetId().m_node)->GetPropertyStatus() == PropertyStatus::Setter;
}
bool IsCodeConstructable(Grammar::VariableConstPtr value)
{
return Data::IsValueType(value->m_datum.GetType())
// || value->m_datum.GetType().GetAZType() == azrtti_typeid<Nodeable>() // <--- cut this.. can't be needed or correct
|| value->m_datum.GetAsDanger() == nullptr;
;
}
bool IsCycle(const Node& node)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Logic::Cycle>(node);
}
bool IsCycle(const ExecutionTreeConstPtr& execution)
{
return execution->GetId().m_node
&& IsCycle(*execution->GetId().m_node)
&& execution->GetId().m_slot->GetType() == CombinedSlotType::ExecutionIn;
}
bool IsDetectableSelfDeactivation(const ExecutionTreeConstPtr& execution)
{
if (execution->GetSymbol() == Symbol::FunctionCall)
{
if (auto methodNode = azrtti_cast<const Nodes::Core::Method*>(execution->GetId().m_node))
{
if (auto behaviorMethod = methodNode->GetMethod())
{
if (AZ::FindAttribute(AZ::ScriptCanvasAttributes::DeactivatesInputEntity, behaviorMethod->m_attributes))
{
if (execution->GetInputCount() == 1 && !execution->GetInput(0).m_slot->IsConnected() && IsInputSelf(execution, 0))
{
return true;
}
}
}
}
}
return false;
}
bool IsEntityIdThatRequiresRuntimeRemap(const VariableConstPtr& variable)
{
if (auto candidate = variable->m_datum.GetAs<Data::EntityIDType>())
{
return variable->m_isExposedToConstruction || RequiresRuntimeRemap(*candidate);
}
else if (auto candidate2 = variable->m_datum.GetAs<Data::NamedEntityIDType>())
{
return variable->m_isExposedToConstruction || RequiresRuntimeRemap(*candidate2);
}
else
{
return false;
}
}
bool IsEventConnectCall(const ExecutionTreeConstPtr& execution)
{
return execution->GetSymbol() == Symbol::FunctionCall
&& CheckEventHandlingType(execution) == EventHandingType::Event
&& execution->GetId().m_slot == AzEventHandlerProperty::GetConnectSlot(execution->GetId().m_node);
}
bool IsEventDisconnectCall(const ExecutionTreeConstPtr& execution)
{
return execution->GetSymbol() == Symbol::FunctionCall
&& CheckEventHandlingType(execution) == EventHandingType::Event
&& execution->GetId().m_slot == AzEventHandlerProperty::GetDisconnectSlot(execution->GetId().m_node);
}
bool IsExecutedPropertyExtraction(const ExecutionTreeConstPtr& execution)
{
return execution->GetExecutedPropertyExtraction() != nullptr;
}
bool IsExternallyInitialized(VariableConstPtr value)
{
const auto requirement = ParseConstructionRequirement(value);
return !(requirement == VariableConstructionRequirement::None || requirement == VariableConstructionRequirement::Static);
}
bool IsFloatingPointNumberEqualityComparison(ExecutionTreeConstPtr execution)
{
const auto symbol = execution->GetSymbol();
return (symbol == Symbol::CompareEqual || symbol == Symbol::CompareEqual)
&& execution->GetInputCount() == 2
&& execution->GetInput(0).m_value->m_datum.GetType() == Data::Type::Number()
&& execution->GetInput(1).m_value->m_datum.GetType() == Data::Type::Number();
}
bool IsFlowControl(const ExecutionTreeConstPtr& execution)
{
// \note this grammar check matches AbstractCodeModel::ParseExecutionTreeBody, and needs to be merged with and replace ParseExecutionFunctionRecurse
return IsBreak(execution)
|| IsCycle(execution)
|| IsForEach(execution)
|| IsIfCondition(execution)
|| IsOnce(execution)
|| IsRandomSwitchStatement(execution)
|| IsSequenceNode(execution)
|| IsSwitchStatement(execution)
|| IsUserOutNode(execution)
|| IsWhileLoop(execution);
}
bool IsForEach(const ExecutionTreeConstPtr& execution)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Core::ForEach*>(execution->GetId().m_node)
&& execution->GetId().m_slot
&& execution->GetId().m_slot->GetType() == CombinedSlotType::ExecutionIn
;
}
bool IsFunctionCallNullCheckRequired(const ExecutionTreeConstPtr& execution)
{
return execution->GetSymbol() == Symbol::FunctionCall
&& execution->GetInputCount() > 0
&& execution->GetInput(0).m_value->m_requiresNullCheck;
}
bool IsGlobalPropertyRead(ExecutionTreeConstPtr execution)
{
return execution->GetSymbol() == Symbol::FunctionCall
&& execution->GetInputCount() == 0
&& ParsingUtilitiesCpp::IsBehaviorContextPropertyRead(execution->GetId().m_node, execution->GetId().m_slot);
}
bool IsGlobalPropertyWrite(ExecutionTreeConstPtr execution)
{
return execution->GetSymbol() == Symbol::FunctionCall
&& execution->GetInputCount() == 0
&& ParsingUtilitiesCpp::IsBehaviorContextPropertyWrite(execution->GetId().m_node, execution->GetId().m_slot);
}
bool IsIfCondition(const ExecutionTreeConstPtr& execution)
{
return execution->GetId().m_node->IsIfBranch()
&& execution->GetId().m_slot->GetType() == CombinedSlotType::ExecutionIn;
}
bool IsInfiniteSelfEntityActivationLoop(const AbstractCodeModel& model, ExecutionTreeConstPtr execution)
{
return (azrtti_istypeof<const Nodes::Core::Start*>(execution->GetId().m_node) || IsOnSelfEntityActivated(model, execution))
&& IsInfiniteSelfEntityActivationLoopRecurse(model, execution);
}
bool IsInfiniteSelfEntityActivationLoopRecurse(const AbstractCodeModel& model, ExecutionTreeConstPtr execution)
{
if (!execution)
{
return false;
}
auto id = execution->GetId();
if (ActivatesSelf(execution))
{
return true;
}
for (size_t index = 0, sentinel = execution->GetChildrenCount(); index < sentinel; ++index)
{
if (IsInfiniteSelfEntityActivationLoopRecurse(model, execution->GetChild(index).m_execution))
{
return true;
}
}
return false;
}
bool IsInfiniteVariableWriteHandlingLoop(const AbstractCodeModel& model, VariableWriteHandlingPtr variableHandling, ExecutionTreeConstPtr execution, bool isConnected)
{
if (!execution)
{
return false;
}
auto id = execution->GetId();
if (isConnected)
{
if (ExecutionWritesVariable(execution, variableHandling->m_variable))
{
return true;
}
else if (id.m_node
&& id.m_slot == id.m_node->GetEBusDisconnectSlot()
&& model.GetVariableHandling(id.m_node->GetEBusConnectAddressSlot()) == variableHandling)
{
isConnected = false;
}
}
else if (!isConnected
&& id.m_node
&& id.m_slot == id.m_node->GetEBusDisconnectSlot()
&& model.GetVariableHandling(id.m_node->GetEBusConnectAddressSlot()) == variableHandling)
{
isConnected = true;
}
for (size_t index = 0, sentinel = execution->GetChildrenCount(); index < sentinel; ++index)
{
if (IsInfiniteVariableWriteHandlingLoop(model, variableHandling, execution->GetChild(index).m_execution, isConnected))
{
return true;
}
}
return false;
}
bool IsInLoop(const ExecutionTreeConstPtr& execution)
{
auto parent = execution->GetParent();
if (!parent)
{
return false;
}
auto id = execution->GetId();
if (IsLooping(parent->GetSymbol())
&& id.m_slot
&& id.m_node
&& id.m_slot->GetId() == id.m_node->GetLoopSlotId())
{
return true;
}
return IsInLoop(parent);
}
bool IsInputSelf(const ExecutionTreeConstPtr& execution, size_t index)
{
return execution->GetInputCount() > index
&& ((execution->GetInput(index).m_value->m_datum.GetAs<Data::NamedEntityIDType>() && *execution->GetInput(index).m_value->m_datum.GetAs<Data::NamedEntityIDType>() == GraphOwnerId && !execution->GetInput(index).m_value->m_isExposedToConstruction)
|| (execution->GetInput(index).m_value->m_datum.GetAs<Data::EntityIDType>() && *execution->GetInput(index).m_value->m_datum.GetAs<Data::EntityIDType>() == GraphOwnerId && !execution->GetInput(index).m_value->m_isExposedToConstruction));
}
bool IsIsNull(const ExecutionTreeConstPtr& execution)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Logic::IsNull>(execution->GetId().m_node)
&& execution->GetId().m_slot
&& execution->GetId().m_slot->GetType() == CombinedSlotType::ExecutionIn
;
}
bool IsLogicalExpression(const ExecutionTreeConstPtr& execution)
{
const auto symbol = execution->GetSymbol();
return symbol == Symbol::CompareEqual
|| symbol == Symbol::CompareGreater
|| symbol == Symbol::CompareGreaterEqual
|| symbol == Symbol::CompareLess
|| symbol == Symbol::CompareLessEqual
|| symbol == Symbol::CompareNotEqual
|| symbol == Symbol::IsNull
|| symbol == Symbol::LogicalAND
|| symbol == Symbol::LogicalNOT
|| symbol == Symbol::LogicalOR
;
}
bool IsLooping(Symbol symbol)
{
return symbol == Symbol::ForEach
|| symbol == Symbol::While;
}
bool IsManuallyDeclaredUserVariable(VariableConstPtr variable)
{
return variable
&& variable->m_source == nullptr
&& !variable->m_sourceSlotId.IsValid()
&& variable->m_sourceVariableId.IsValid()
&& !variable->m_nodeableNodeId.IsValid();
}
bool IsMidSequence(const ExecutionTreeConstPtr& execution)
{
if (!execution)
{
return false;
}
auto parent = execution->GetParent();
if (!parent)
{
return false;
}
const size_t childrenCount = parent->GetChildrenCount();
if (childrenCount == 0)
{
return false;
}
if (parent->GetSymbol() == Symbol::Sequence)
{
size_t childIndex = parent->FindChildIndex(execution);
if (childIndex < (childrenCount - 1))
{
return true;
}
}
return IsMidSequence(parent);
}
bool IsNoOp(const AbstractCodeModel& model, const ExecutionTreeConstPtr& parent, const Grammar::ExecutionChild& child)
{
const ExecutionTreeConstPtr& execution = child.m_execution;
if (IsLooping(parent->GetSymbol()))
{
return false;
}
if (child.m_execution->GetSymbol() == Symbol::VariableAssignment)
{
auto dataSlots = child.m_execution->GetId().m_node->GetOnVariableHandlingDataSlots();
if (dataSlots.size() > 0)
{
bool isNoOp = true;
for (auto dataSlot : dataSlots)
{
auto writeHandling = model.GetVariableHandling(dataSlot);
if (!writeHandling || writeHandling->RequiresConnectionControl())
{
isNoOp = false;
break;
}
}
if (isNoOp)
{
return true;
}
}
}
if (IsPropertyExtractionNode(execution) && !IsExecutedPropertyExtraction(execution))
{
return true;
}
if (execution->GetSymbol() == Symbol::PlaceHolderDuringParsing)
{
return true;
}
if (!execution->GetId().m_node)
{
return false;
}
if (execution->GetId().m_node->IsNoOp())
{
return true;
}
return false;
}
bool IsOnce(const Node& node)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Logic::Once>(node);
}
bool IsOnce(const ExecutionTreeConstPtr& execution)
{
return execution->GetId().m_node
&& IsOnce(*execution->GetId().m_node)
&& execution->GetId().m_slot
&& execution->GetId().m_slot->GetType() == CombinedSlotType::ExecutionIn;
}
bool IsOnceIn(const Node& node, const Slot* slot)
{
return slot == OnceProperty::GetInSlot(&node);
}
bool IsOnceReset(const Node& node, const Slot* slot)
{
return slot == OnceProperty::GetResetSlot(&node);
}
bool IsOperatorArithmetic(const ExecutionTreeConstPtr& execution)
{
const auto symbol = execution->GetSymbol();
return symbol == Symbol::OperatorAddition
|| symbol == Symbol::OperatorDivision
|| symbol == Symbol::OperatorMultiplication
|| symbol == Symbol::OperatorSubraction
;
}
bool IsOnSelfEntityActivated(const AbstractCodeModel&, ExecutionTreeConstPtr execution)
{
auto id = execution->GetId();
auto eventHandlerNode = azrtti_cast<const Nodes::Core::EBusEventHandler*>(id.m_node);
if (!eventHandlerNode)
{
return false;
}
AZ::BehaviorEBus* ebus = eventHandlerNode->GetBus();
if (ebus->m_name != "EntityBus")
{
return false;
}
if (!id.m_slot)
{
return false;
}
const Nodes::Core::EBusEventEntry* entry = eventHandlerNode->FindEventWithSlot(*id.m_slot);
if (!entry)
{
return false;
}
if (entry->m_eventName != "OnEntityActivated")
{
return false;
}
Data::EntityIDType entityAddress;
if (auto addressDatum = eventHandlerNode->GetHandlerStartAddress())
{
if (auto entityId = addressDatum->GetAs<Data::EntityIDType>())
{
entityAddress = *entityId;
}
else if (auto namedEntityId = addressDatum->GetAs<Data::NamedEntityIDType>())
{
entityAddress = *namedEntityId;
}
}
if (entityAddress != GraphOwnerId)
{
return false;
}
return true;
}
bool IsPropertyExtractionSlot(const ExecutionTreeConstPtr& execution, const Slot* outputSlot)
{
auto iter = AZStd::find_if
( execution->GetPropertyExtractionSources().begin()
, execution->GetPropertyExtractionSources().end()
, [&](const auto& iter) { return iter.first == outputSlot; });
return iter != execution->GetPropertyExtractionSources().end();
}
bool IsPropertyExtractionNode(const ExecutionTreeConstPtr& execution)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Core::ExtractProperty*>(execution->GetId().m_node);
}
bool IsPure(Symbol symbol)
{
return symbol != Symbol::Cycle;
}
bool IsPure(const Node* node, const Slot* slot)
{
if (auto userFunctionCall = azrtti_cast<const ScriptCanvas::Nodes::Core::FunctionCallNode*>(node))
{
if (!userFunctionCall->IsSlotPure(slot))
{
return false;
}
}
else if (node && IsOnce(*node))
{
return false;
}
return true;
}
bool IsRandomSwitchStatement(const ExecutionTreeConstPtr& execution)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Logic::WeightedRandomSequencer>(execution->GetId().m_node)
&& execution->GetId().m_slot->GetType() == CombinedSlotType::ExecutionIn;
}
bool IsSequenceNode(const Node* node)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Logic::OrderedSequencer*>(node);
}
bool IsSequenceNode(const ExecutionTreeConstPtr& execution)
{
return (IsSequenceNode(execution->GetId().m_node)
&& execution->GetId().m_slot->GetType() == CombinedSlotType::ExecutionIn);
}
bool IsSwitchStatement(const ExecutionTreeConstPtr& execution)
{
return execution->GetId().m_node->IsSwitchStatement()
&& execution->GetId().m_slot->GetType() == CombinedSlotType::ExecutionIn;
}
bool IsUserFunctionCall(const ExecutionTreeConstPtr& execution)
{
return (execution->GetSymbol() == Symbol::FunctionCall)
&& azrtti_istypeof<const ScriptCanvas::Nodes::Core::FunctionCallNode*>(execution->GetId().m_node);
}
bool IsUserFunctionCallPure(const ExecutionTreeConstPtr& execution)
{
return (execution->GetSymbol() == Symbol::FunctionCall)
&& azrtti_istypeof<const ScriptCanvas::Nodes::Core::FunctionCallNode*>(execution->GetId().m_node)
&& azrtti_cast<const ScriptCanvas::Nodes::Core::FunctionCallNode*>(execution->GetId().m_node)->IsPure();
}
bool IsUserFunctionDefinition(const ExecutionTreeConstPtr& execution)
{
auto nodeling = azrtti_cast<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>(execution->GetId().m_node);
return nodeling && nodeling->IsExecutionEntry() && execution->GetSymbol() == Symbol::FunctionDefinition;
}
const ScriptCanvas::Nodes::Core::FunctionDefinitionNode* IsUserOutNode(const Node* node)
{
auto nodeling = azrtti_cast<const ScriptCanvas::Nodes::Core::FunctionDefinitionNode*>(node);
return nodeling && nodeling->IsExecutionExit() ? nodeling : nullptr;
}
const ScriptCanvas::Nodes::Core::FunctionDefinitionNode* IsUserOutNode(const ExecutionTreeConstPtr& execution)
{
auto nodeling = IsUserOutNode(execution->GetId().m_node);
return nodeling ? nodeling : nullptr;
}
bool IsVariableGet(const ExecutionTreeConstPtr& execution)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Core::GetVariableNode*>(execution->GetId().m_node)
&& !IsExecutedPropertyExtraction(execution);
}
bool IsVariableSet(const ExecutionTreeConstPtr& execution)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Core::SetVariableNode*>(execution->GetId().m_node)
&& !IsExecutedPropertyExtraction(execution);
}
bool IsWhileLoop(const ExecutionTreeConstPtr& execution)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Logic::While*>(execution->GetId().m_node)
&& execution->GetId().m_slot->GetType() == CombinedSlotType::ExecutionIn;
}
bool IsWrittenMathExpression(const ExecutionTreeConstPtr& execution)
{
return azrtti_istypeof<const ScriptCanvas::Nodes::Math::MathExpression*>(execution->GetId().m_node);
}
AZStd::string MakeMemberVariableName(AZStd::string_view name)
{
return AZStd::string::format("%s%s", k_memberNamePrefix, name.data());
}
VariableConstructionRequirement ParseConstructionRequirement(VariableConstPtr variable)
{
if (IsEntityIdThatRequiresRuntimeRemap(variable))
{
return VariableConstructionRequirement::InputEntityId;
}
else if (variable->m_isExposedToConstruction)
{
if (variable->m_nodeableNodeId.IsValid())
{
return VariableConstructionRequirement::InputNodeable;
}
else if (variable->m_sourceVariableId.IsValid())
{
return VariableConstructionRequirement::InputVariable;
}
else
{
AZ_Assert(false, "A member variable in the model has no valid id");
}
}
else if (variable->m_sourceVariableId.IsValid() && !IsCodeConstructable(variable))
{
return VariableConstructionRequirement::Static;
}
return VariableConstructionRequirement::None;
}
void ParseVariableUse(const OutputAssignment& outputAssignment, VariableUseage& variableUse)
{
variableUse.Parse(outputAssignment.m_source);
for (const auto& assignment : outputAssignment.m_assignments)
{
variableUse.Parse(assignment);
}
}
void ParseVariableUse(ExecutionTreeConstPtr execution, VariableUseage& variableUse)
{
for (size_t inputIdx = 0; inputIdx != execution->GetInputCount(); ++inputIdx)
{
variableUse.Parse(execution->GetInput(inputIdx).m_value);
}
for (size_t returnIdx = 0; returnIdx != execution->GetReturnValueCount(); ++returnIdx)
{
if (auto returnValue = execution->GetReturnValue(returnIdx).second)
{
ParseVariableUse(*returnValue, variableUse);
}
}
if (auto localOutputPtr = execution->GetLocalOutput())
{
for (const auto& localOuput : *localOutputPtr)
{
if (localOuput.second)
{
return ParseVariableUse(*localOuput.second, variableUse);
}
}
}
else
{
for (size_t childIdx = 0; childIdx != execution->GetChildrenCount(); ++childIdx)
{
const auto& child = execution->GetChild(childIdx);
for (const auto& localOuput : child.m_output)
{
if (localOuput.second)
{
ParseVariableUse(*localOuput.second, variableUse);
}
}
}
}
}
void PrettyPrint(AZStd::string& result, const AbstractCodeModel& model)
{
if (model.IsPerEntityDataRequired())
{
result += "* Per entity data required\n";
}
else
{
result += "* Pure functionality\n";
}
for (auto& variable : model.GetVariables())
{
result += AZStd::string::format("Variable: %s, Type: %s, Scope: %s, \n"
, variable->m_name.data()
, Data::GetName(variable->m_datum.GetType()).data()
, variable->m_isMember ? "Member" : "Local" );
}
auto roots = model.GetAllExecutionRoots();
for (auto& root : roots)
{
ParsingUtilitiesCpp::PrettyPrinter printer;
TraverseTree(root, printer);
result += printer.TakeResult();
}
}
void PrettyPrint(AZStd::string& result, const ExecutionTreeConstPtr& execution, ExecutionTreeConstPtr marker)
{
ParsingUtilitiesCpp::PrettyPrinter printer(marker);
TraverseTree(execution, printer);
result = printer.TakeResult();
}
void ProtectReservedWords(AZStd::string& name)
{
if (!name.ends_with(k_reservedWordProtection))
{
name.append(k_reservedWordProtection);
}
}
OutputAssignmentPtr RemoveOutput(ExecutionChild& executionChild, const SlotId& slotId)
{
auto iter = AZStd::find_if(executionChild.m_output.begin(), executionChild.m_output.end(), [&](const auto& candidate) { return candidate.first && candidate.first->GetId() == slotId; });
if (iter != executionChild.m_output.end())
{
auto output = iter->second;
executionChild.m_output.erase(iter);
return AZStd::const_pointer_cast<OutputAssignment>(output);
}
else
{
return nullptr;
}
}
bool RequiresRuntimeRemap(const AZ::EntityId& entityId)
{
return entityId.IsValid()
&& entityId != UniqueId
&& entityId != GraphOwnerId;
}
AZStd::string SlotNameToIndexString(const Slot& slot)
{
auto indexString = slot.GetName();
indexString.erase(0, strlen("Out "));
return indexString;
}
AZStd::string ToIdentifier(AZStd::string_view name)
{
auto identifier = AZ::ReplaceCppArtifacts(name);
AZ::StringFunc::Replace(identifier, " ", "_", true);
DenumberFirstCharacter(identifier);
return identifier;
}
AZStd::string ToIdentifierSafe(AZStd::string_view name)
{
auto identifier = AZ::ReplaceCppArtifacts(name);
AZ::StringFunc::Replace(identifier, " ", "_", true);
DenumberFirstCharacter(identifier);
ProtectReservedWords(identifier);
return identifier;
}
ExecutionTraversalResult TraverseExecutionConnectionsRecurse(const EndpointsResolved& nextEndpoints, AZStd::unordered_set<const Slot*>& previousIns, GraphExecutionPathTraversalListener& listener);
ExecutionTraversalResult TraverseExecutionConnectionsRecurse(const EndpointResolved& in, AZStd::unordered_set<const Slot*>& previousIns, GraphExecutionPathTraversalListener& listener);
ExecutionTraversalResult TraverseExecutionConnections(const Node& node, const Slot& outSlot, GraphExecutionPathTraversalListener& listener)
{
AZStd::unordered_set<const Slot*> path;
return TraverseExecutionConnectionsRecurse({ &node, &outSlot }, path, listener);
}
ExecutionTraversalResult TraverseExecutionConnectionsRecurse(const EndpointsResolved& nextEndpoints, AZStd::unordered_set<const Slot*>& previousPath, GraphExecutionPathTraversalListener& listener)
{
if (listener.CancelledTraversal())
{
return ExecutionTraversalResult::Success;
}
if (!nextEndpoints.empty())
{
if (nextEndpoints.size() == 1)
{
auto status = TraverseExecutionConnectionsRecurse(nextEndpoints[0], previousPath, listener);
if (status != ExecutionTraversalResult::Success)
{
return status;
}
}
else
{
// Subsequent connections after an the multiple Execution Out connections syntax sugar
// only have to check for loops up to the sequence point.
// Duplicate endpoints after the sequence are not necessarily loops, but are likely just the normal
// ScriptCanvas way allowing users to use the same visual path (thus preventing "duplicate code").
for (auto nextEndpoint : nextEndpoints)
{
AZStd::unordered_set<const Slot*> pathUpToSequenceSyntaxSugar(previousPath);
auto status = TraverseExecutionConnectionsRecurse(nextEndpoint, pathUpToSequenceSyntaxSugar, listener);
if (status != ExecutionTraversalResult::Success)
{
return status;
}
}
}
}
return ExecutionTraversalResult::Success;
}
ExecutionTraversalResult TraverseExecutionConnectionsRecurse(const EndpointResolved& in, AZStd::unordered_set<const Slot*>& previousPath, GraphExecutionPathTraversalListener& listener)
{
if (previousPath.contains(in.second))
{
return ExecutionTraversalResult::ContainsCycle;
}
if (!in.second)
{
return ExecutionTraversalResult::NullSlot;
}
listener.Evaluate(in);
if (listener.CancelledTraversal())
{
return ExecutionTraversalResult::Success;
}
AZStd::vector<const Slot*> outSlots;
if (in.second->IsLatent())
{
outSlots.push_back(in.second);
}
else
{
previousPath.insert(in.second);
auto outSlotsOutcome = in.first->GetSlotsInExecutionThreadByType(*in.second, CombinedSlotType::ExecutionOut);
if (!outSlotsOutcome.IsSuccess())
{
return ExecutionTraversalResult::GetSlotError;
}
outSlots = outSlotsOutcome.GetValue();
}
for (auto branch : outSlots)
{
auto nextEndpoints = in.first->GetConnectedNodes(*branch);
AZStd::unordered_set<const Slot*> pathUpToBranchOrSyntaxSugar(previousPath);
const auto status = TraverseExecutionConnectionsRecurse(nextEndpoints, pathUpToBranchOrSyntaxSugar, listener);
if (status != ExecutionTraversalResult::Success)
{
return status;
}
}
return ExecutionTraversalResult::Success;
}
void TraverseTreeRecurse(const ExecutionTreeConstPtr& execution, ExecutionTreeTraversalListener& listener, const Slot* slot, int depth = 0);
void TraverseTree(const AbstractCodeModel& model, ExecutionTreeTraversalListener& listener)
{
for (auto root : model.GetAllExecutionRoots())
{
TraverseTree(root, listener);
}
}
void TraverseTree(const ExecutionTreeConstPtr& execution, ExecutionTreeTraversalListener& listener)
{
listener.Reset();
TraverseTreeRecurse(execution, listener, nullptr, 0);
}
void TraverseTreeRecurse(const ExecutionTreeConstPtr& execution, ExecutionTreeTraversalListener& listener, const Slot* slot, int level)
{
if (listener.CancelledTraversal())
{
return;
}
if (!execution->GetParent())
{
listener.EvaluateRoot(execution, slot);
}
listener.Evaluate(execution, slot, level);
if (execution->GetChildrenCount() == 0 && !IsInLoop(execution) && !IsMidSequence(execution))
{
listener.EvaluateLeaf(execution, slot, level);
}
else
{
for (size_t i(0); i < execution->GetChildrenCount(); ++i)
{
auto& childIter = execution->GetChild(i);
if (childIter.m_execution)
{
listener.EvaluateChildPre(execution, childIter.m_slot, i, level + 1);
TraverseTreeRecurse(childIter.m_execution, listener, childIter.m_slot, level + 1);
listener.EvaluateChildPost(execution, childIter.m_slot, i, level + 1);
}
else if (!IsInLoop(execution) && !IsMidSequence(execution))
{
listener.EvaluateNullChildLeaf(execution, childIter.m_slot, i, level + 1);
}
}
}
}
}
}