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
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
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
3 changes: 0 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 Down
27 changes: 24 additions & 3 deletions Source/Flow/Public/Nodes/FlowNodeBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,20 +335,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