Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 56 additions & 3 deletions Source/Flow/Private/FlowAsset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
#include "Editor/EditorEngine.h"

FString UFlowAsset::ValidationError_NodeClassNotAllowed = TEXT("Node class {0} is not allowed in this asset.");
FString UFlowAsset::ValidationError_AddOnNodeClassNotAllowed = TEXT("AddOn Node class {0} is not allowed in this asset.");
FString UFlowAsset::ValidationError_NullNodeInstance = TEXT("Node with GUID {0} is NULL");
FString UFlowAsset::ValidationError_NullAddOnNodeInstance = TEXT("Node with GUID {0} has NULL AddOn(s)");
#endif

#include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAsset)
Expand Down Expand Up @@ -122,9 +124,21 @@ EDataValidationResult UFlowAsset::ValidateAsset(FFlowMessageLog& MessageLog)
}

Node.Value->ValidationLog.Messages.Empty();
if (Node.Value->ValidateNode() == EDataValidationResult::Invalid)
Node.Value->ValidateNode();
MessageLog.Messages.Append(Node.Value->ValidationLog.Messages);

// Validate AddOns
for (UFlowNodeAddOn* AddOn : Node.Value->GetFlowNodeAddOnChildren())
{
MessageLog.Messages.Append(Node.Value->ValidationLog.Messages);
if (IsValid(AddOn))
{
ValidateAddOnTree(*AddOn, MessageLog);
}
else
{
const FString ErrorMsg = FString::Format(*ValidationError_NullAddOnNodeInstance, { *Node.Key.ToString() });
MessageLog.Error(*ErrorMsg, this);
}
}
}
else
Expand All @@ -134,7 +148,17 @@ EDataValidationResult UFlowAsset::ValidateAsset(FFlowMessageLog& MessageLog)
}
}

return MessageLog.Messages.Num() > 0 ? EDataValidationResult::Invalid : EDataValidationResult::Valid;
// if at least one error has been has been logged : mark the asset as invalid
for (const TSharedRef<FTokenizedMessage>& Msg : MessageLog.Messages)
{
if (Msg->GetSeverity() == EMessageSeverity::Error)
{
return EDataValidationResult::Invalid;
}
}

// otherwise, the asset is considered valid (even with warnings or notes)
return EDataValidationResult::Valid;
}

bool UFlowAsset::IsNodeOrAddOnClassAllowed(const UClass* FlowNodeOrAddOnClass, FText* OutOptionalFailureReason) const
Expand Down Expand Up @@ -239,6 +263,35 @@ bool UFlowAsset::IsFlowNodeClassInDeniedClasses(const UClass& FlowNodeClass) con
return false;
}

void UFlowAsset::ValidateAddOnTree(UFlowNodeAddOn& AddOn, FFlowMessageLog& MessageLog)
{
// Filter unauthorized addon nodes
FText FailureReason;
if (!IsNodeOrAddOnClassAllowed(AddOn.GetClass(), &FailureReason))
{
const FString ErrorMsg =
FailureReason.IsEmpty()
? FString::Format(*ValidationError_AddOnNodeClassNotAllowed, { *AddOn.GetClass()->GetName() })
: FailureReason.ToString();

MessageLog.Error(*ErrorMsg, AddOn.GetFlowNodeSelfOrOwner());
}

// Validate AddOn
AddOn.ValidationLog.Messages.Empty();
AddOn.ValidateNode();
MessageLog.Messages.Append(AddOn.ValidationLog.Messages);

// Validate Children
for (UFlowNodeAddOn* Child : AddOn.GetFlowNodeAddOnChildren())
{
if (IsValid(Child))
{
ValidateAddOnTree(*Child, MessageLog);
}
}
}

bool UFlowAsset::IsFlowNodeClassInAllowedClasses(const UClass& FlowNodeClass,
const TSubclassOf<UFlowNodeBase>& RequiredAncestor) const
{
Expand Down
6 changes: 5 additions & 1 deletion Source/Flow/Private/FlowMessageLog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ FFlowGraphToken::FFlowGraphToken(const UFlowAsset* InFlowAsset)
}

FFlowGraphToken::FFlowGraphToken(const UFlowNodeBase* InFlowNodeBase)
: GraphNode(InFlowNodeBase->GetGraphNode())
: GraphNode(
InFlowNodeBase->GetParentNode()
? InFlowNodeBase->GetParentNode()->GetGraphNode()
: nullptr
)
{
CachedText = InFlowNodeBase->GetNodeTitle();
}
Expand Down
32 changes: 32 additions & 0 deletions Source/Flow/Private/Nodes/FlowNodeBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,44 @@ TArray<FFlowPin> UFlowNodeBase::GetContextOutputs() const
return ContextOutputs;
}

EDataValidationResult UFlowNodeBase::ValidateNode()
{
if (GetClass()->IsFunctionImplementedInScript(GET_FUNCTION_NAME_CHECKED(UFlowNodeBase, K2_ValidateNode)))
{
return K2_ValidateNode();
}

return EDataValidationResult::NotValidated;
}

FString UFlowNodeBase::GetStatusString() const
{
return K2_GetStatusString();
}

#endif // WITH_EDITOR

void UFlowNodeBase::LogValidationError(const FString& Message)
{
#if WITH_EDITOR
ValidationLog.Error<UFlowNodeBase>(*Message, this);
#endif
}

void UFlowNodeBase::LogValidationWarning(const FString& Message)
{
#if WITH_EDITOR
ValidationLog.Warning<UFlowNodeBase>(*Message, this);
#endif
}

void UFlowNodeBase::LogValidationNote(const FString& Message)
{
#if WITH_EDITOR
ValidationLog.Note<UFlowNodeBase>(*Message, this);
#endif
}

UFlowAsset* UFlowNodeBase::GetFlowAsset() const
{
// In the case of an AddOn, we want our containing FlowNode's Outer, not our own
Expand Down
23 changes: 23 additions & 0 deletions Source/Flow/Public/AddOns/FlowNodeAddOn.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class UFlowNodeAddOn : public UFlowNodeBase
// (accessible only when initialized, runtime only)
UPROPERTY(Transient)
TObjectPtr<UFlowNode> FlowNode;

#if WITH_EDITORONLY_DATA
// Editor-only pointer to the owning top-level UFlowNode
UPROPERTY(Transient)
TObjectPtr<UFlowNode> ParentNode;
#endif

// Input pins to add to the owning flow node
// If defined, ExecuteInput will only be executed for these inputs
Expand Down Expand Up @@ -78,6 +84,23 @@ class UFlowNodeAddOn : public UFlowNodeBase
// by default, uses the seed for the Flow Node that this addon is attached to.
FLOW_API virtual int32 GetRandomSeed() const override;

// Sets the parent node. Editor only.
FLOW_API virtual void SetParentNode(UFlowNode* InParent)
{
#if WITH_EDITORONLY_DATA
ParentNode = InParent;
#endif // WITH_EDITOR
}

// Editor only.
FLOW_API virtual const UFlowNode* GetParentNode() const override
{
#if WITH_EDITORONLY_DATA
if (ParentNode) return ParentNode;
#endif // WITH_EDITOR
return UFlowNodeBase::GetFlowNodeSelfOrOwner();
}

#if WITH_EDITOR
// IFlowContextPinSupplierInterface
FLOW_API virtual bool SupportsContextPins() const override { return Super::SupportsContextPins() || (!InputPins.IsEmpty() || !OutputPins.IsEmpty()); }
Expand Down
6 changes: 6 additions & 0 deletions Source/Flow/Public/FlowAsset.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ class FLOW_API UFlowAsset : public UObject
FSimpleDelegate OnDetailsRefreshRequested;

static FString ValidationError_NodeClassNotAllowed;
static FString ValidationError_AddOnNodeClassNotAllowed;
static FString ValidationError_NullNodeInstance;
static FString ValidationError_NullAddOnNodeInstance;

private:
UPROPERTY()
Expand All @@ -126,6 +128,10 @@ class FLOW_API UFlowAsset : public UObject

bool IsFlowNodeClassInAllowedClasses(const UClass& FlowNodeClass, const TSubclassOf<UFlowNodeBase>& RequiredAncestor = nullptr) const;
bool IsFlowNodeClassInDeniedClasses(const UClass& FlowNodeClass) const;

private:
// Recursively validates the given addon and its children.
void ValidateAddOnTree(UFlowNodeAddOn& AddOn, FFlowMessageLog& MessageLog);
#endif

//////////////////////////////////////////////////////////////////////////
Expand Down
8 changes: 5 additions & 3 deletions Source/Flow/Public/Nodes/FlowNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,6 @@ class FLOW_API UFlowNode
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
virtual void PostLoad() override;
// --

virtual EDataValidationResult ValidateNode() { return EDataValidationResult::NotValidated; }

#endif

// Inherits Guid after graph node
Expand All @@ -78,6 +75,11 @@ class FLOW_API UFlowNode
// by default based on the node Guid,
// but may be overridden in subclasses to supply some other value.
virtual int32 GetRandomSeed() const override { return GetTypeHash(NodeGuid); }

virtual const UFlowNode* GetParentNode() const override
{
return UFlowNodeBase::GetFlowNodeSelfOrOwner();
}

public:
virtual bool CanFinishGraph() const { return false; }
Expand Down
30 changes: 27 additions & 3 deletions Source/Flow/Public/Nodes/FlowNodeBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ class FLOW_API UFlowNodeBase
UFUNCTION(BlueprintPure, Category = "FlowNode")
virtual int32 GetRandomSeed() const PURE_VIRTUAL(GetRandomSeed, return 0;);

// Returns the owning top-level Flow node.
virtual const UFlowNode* GetParentNode() const PURE_VIRTUAL(GetParentNode, return nullptr;);

//////////////////////////////////////////////////////////////////////////
// Pins

Expand Down Expand Up @@ -335,20 +338,41 @@ class FLOW_API UFlowNodeBase

// used when import graph from another asset
virtual void PostImport() {}

void RequestReconstruction() const { (void) OnReconstructionRequested.ExecuteIfBound(); };

virtual EDataValidationResult ValidateNode();

// Called by owning FlowNode to add to its Status String.
// (may be multi-line)
virtual FString GetStatusString() const;

void RequestReconstruction() const { (void) OnReconstructionRequested.ExecuteIfBound(); };

#endif
#endif // WITH_EDITOR

protected:
// Information displayed while node is working - displayed over node as NodeInfoPopup
UFUNCTION(BlueprintImplementableEvent, Category = "FlowNode", meta = (DisplayName = "Get Status String"))
FString K2_GetStatusString() const;

// Flow Node Validation : blueprint compatibility

// Optional validation override for Blueprints
UFUNCTION(BlueprintImplementableEvent, Category = "FlowNode|Validation", meta = (DisplayName = "Validate Node", DevelopmentOnly))
EDataValidationResult K2_ValidateNode();

// Log validation error (editor-only)
UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation", meta = (DevelopmentOnly))
void LogValidationError(const FString& Message);

// Log validation warning (editor-only)
UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation", meta = (DevelopmentOnly))
void LogValidationWarning(const FString& Message);

// Log validation note (editor-only)
UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation", meta = (DevelopmentOnly))
void LogValidationNote(const FString& Message);
// --

#if WITH_EDITORONLY_DATA
protected:
UPROPERTY()
Expand Down
25 changes: 24 additions & 1 deletion Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,27 @@ void UFlowGraphNode::SetParentNodeForSubNode(UFlowGraphNode* InParentNode)
{
// Once a SubNode, always a SubNode
bIsSubNode = true;

#if WITH_EDITOR
// Attempt to set the parent node if the instance is an AddOn
if (UFlowNodeAddOn* SelfAsAddOn = Cast<UFlowNodeAddOn>(NodeInstance))
{
const UFlowNode* TopLevelOwner = nullptr;

if (const UFlowNode* ParentFlowNode = Cast<UFlowNode>(InParentNode->NodeInstance))
{
// Parent is the top-level flow node
TopLevelOwner = ParentFlowNode;
}
else if (const UFlowNodeAddOn* ParentAddOn = Cast<UFlowNodeAddOn>(InParentNode->NodeInstance))
{
// Bubble up to the top-level flow node
TopLevelOwner = ParentAddOn->GetParentNode();
}

SelfAsAddOn->SetParentNode(const_cast<UFlowNode*>(TopLevelOwner));
}
#endif
}

ParentNode = InParentNode;
Expand Down Expand Up @@ -1479,10 +1500,12 @@ void UFlowGraphNode::AddSubNode(UFlowGraphNode* SubNode, class UEdGraph* ParentG

// set outer to be the graph so it doesn't go away
SubNode->Rename(nullptr, ParentGraph, REN_NonTransactional);
SubNode->SetParentNodeForSubNode(this);

SubNode->CreateNewGuid();
SubNode->PostPlacedNewNode();

SubNode->SetParentNodeForSubNode(this);

SubNode->AllocateDefaultPins();
SubNode->AutowireNewNode(nullptr);

Expand Down