ControlRig / RigVM: Offer user workflows per unit / node

#rb sara.schvartzman jack.cai
#jira UE-144670
#preflight https://horde.devtools.epicgames.com/job/624c14e1e434babd8a41478d

[CL 19622287 by Helge Mathee in ue5-main branch]
This commit is contained in:
Helge Mathee
2022-04-05 06:16:59 -04:00
parent 7dd50d8a4f
commit 3fb666f8d9
15 changed files with 626 additions and 10 deletions
@@ -221,6 +221,15 @@ float FRigVMStruct::GetRatioFromIndex(int32 InIndex, int32 InCount)
return ((float)FMath::Clamp<int32>(InIndex, 0, InCount - 1)) / ((float)(InCount - 1));
}
TArray<FRigVMUserWorkflow> FRigVMStruct::GetWorkflows(ERigVMUserWorkflowType InType) const
{
return GetSupportedWorkflows().FilterByPredicate([InType](const FRigVMUserWorkflow& InWorkflow) -> bool
{
return uint32(InWorkflow.GetType()) & uint32(InType) &&
InWorkflow.IsValid();
});
}
#if WITH_EDITOR
bool FRigVMStruct::ValidateStruct(UScriptStruct* InStruct, FString* OutErrorMessage)
@@ -0,0 +1,121 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "RigVMCore/RigVMUserWorkflow.h"
#include "RigVMCore/RigVMMemoryStorage.h"
#include "RigVMModule.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool URigVMUserWorkflowOptions::RequiresDialog() const
{
for (TFieldIterator<FProperty> It(GetClass()); It; ++It)
{
if(const FProperty* Property = CastField<FProperty>(*It))
{
if(RequiresDialog(Property))
{
return true;
}
}
}
return false;
}
bool URigVMUserWorkflowOptions::RequiresDialog(const FProperty* InProperty) const
{
if(!InProperty->HasAnyPropertyFlags(CPF_Edit))
{
return false;
}
static const FName SubjectName = TEXT("Subject");
static const FName WorkflowName = TEXT("Workflow");
return InProperty->GetFName() != SubjectName && InProperty->GetFName() != WorkflowName;
}
void URigVMUserWorkflowOptions::Report(EMessageSeverity::Type InSeverity, const FString& InMessage) const
{
if (ReportDelegate.IsBound())
{
ReportDelegate.Execute(InSeverity, GetSubject(), InMessage);
}
else
{
if (InSeverity == EMessageSeverity::Error)
{
FScriptExceptionHandler::Get().HandleException(ELogVerbosity::Error, *InMessage, *FString());
}
else if (InSeverity == EMessageSeverity::Warning)
{
FScriptExceptionHandler::Get().HandleException(ELogVerbosity::Warning, *InMessage, *FString());
}
else
{
UE_LOG(LogRigVM, Display, TEXT("%s"), *InMessage);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TArray<FRigVMUserWorkflowAction> FRigVMUserWorkflow::GetActions(const URigVMUserWorkflowOptions* InOptions) const
{
TArray<FRigVMUserWorkflowAction> Actions;
if(!IsValid() || !ValidateOptions(InOptions))
{
return Actions;
}
if(OnGetActionsDelegate.IsBound())
{
Actions = OnGetActionsDelegate.Execute(InOptions);
}
else if(OnGetActionsDynamicDelegate.IsBound())
{
Actions = OnGetActionsDynamicDelegate.Execute(InOptions);
}
Actions = Actions.FilterByPredicate([](const FRigVMUserWorkflowAction& Action) -> bool
{
return Action.IsValid();
});
return Actions;
}
bool FRigVMUserWorkflow::ValidateOptions(const URigVMUserWorkflowOptions* InOptions) const
{
UClass* ExpectedOptionsClass = GetOptionsClass();
if(!ensure(ExpectedOptionsClass != nullptr))
{
return false;
}
if(!ensure(ExpectedOptionsClass->IsChildOf(URigVMUserWorkflowOptions::StaticClass())))
{
return false;
}
if(!ensure(InOptions != nullptr))
{
return false;
}
if(!ensure(InOptions->IsValid()))
{
return false;
}
if(!ensure(InOptions->GetClass()->IsChildOf(ExpectedOptionsClass)))
{
InOptions->Reportf(
EMessageSeverity::Error,
TEXT("Workflow '%s' cannot execute: Options are of type '%s' - expected was type '%s'."),
*GetTitle(),
*InOptions->GetClass()->GetName(),
*ExpectedOptionsClass->GetName());
return false;
}
return true;
}
@@ -7,6 +7,7 @@
#include "RigVMCore/RigVMRegistry.h"
#include "RigVMCore/RigVMExternalVariable.h"
#include "RigVMCore/RigVMTraits.h"
#include "RigVMCore/RigVMUserWorkflow.h"
#include "RigVMStruct.generated.h"
// delegates used for variable introspection / creation
@@ -221,6 +222,9 @@ public:
// node creation
FORCEINLINE virtual void OnUnitNodeCreated(FRigVMUnitNodeCreatedContext& InContext) const {}
// user workflow
TArray<FRigVMUserWorkflow> GetWorkflows(ERigVMUserWorkflowType InType = ERigVMUserWorkflowType::All) const;
#if WITH_EDITOR
static bool ValidateStruct(UScriptStruct* InStruct, FString* OutErrorMessage);
static bool CheckPinType(UScriptStruct* InStruct, const FName& PinName, const FString& ExpectedType, FString* OutErrorMessage = nullptr);
@@ -232,7 +236,27 @@ public:
#endif
static FString ExportToFullyQualifiedText(const FProperty* InMemberProperty, const uint8* InMemberMemoryPtr, bool bUseQuotes = true);
static FString ExportToFullyQualifiedText(const UScriptStruct* InStruct, const uint8* InStructMemoryPtr);
template <
typename T,
typename TEnableIf<TRigVMIsBaseStructure<T>::Value>::Type * = nullptr
>
FORCEINLINE static FString ExportToFullyQualifiedText(const T& InStructValue)
{
return ExportToFullyQualifiedText(TBaseStructure<T>::Get(), (const uint8*)&InStructValue);
}
template <
typename T,
typename TEnableIf<TModels<CRigVMUStruct, T>::Value>::Type * = nullptr
>
FORCEINLINE static FString ExportToFullyQualifiedText(const T& InStructValue)
{
return ExportToFullyQualifiedText(T::StaticStruct(), (const uint8*)&InStructValue);
}
FString ExportToFullyQualifiedText(const UScriptStruct* InScriptStruct, const FName& InPropertyName, const uint8* InStructMemoryPointer = nullptr) const;
virtual FName GetNextAggregateName(const FName& InLastAggregatePinName) const { return FName(); }
virtual FRigVMStructUpgradeInfo GetUpgradeInfo() const { return FRigVMStructUpgradeInfo(); }
@@ -276,6 +300,7 @@ protected:
static float GetRatioFromIndex(int32 InIndex, int32 InCount);
TMap<FName, FString> GetDefaultValues(UScriptStruct* InScriptStruct) const;
bool ApplyUpgradeInfo(const FRigVMStructUpgradeInfo& InUpgradeInfo);
FORCEINLINE virtual TArray<FRigVMUserWorkflow> GetSupportedWorkflows() const { return TArray<FRigVMUserWorkflow>(); }
friend struct FRigVMStructUpgradeInfo;
friend class FRigVMGraphStructUpgradeInfoTest;
@@ -0,0 +1,193 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Logging/TokenizedMessage.h"
#include "RigVMCore/RigVMTraits.h"
#include "RigVMUserWorkflow.generated.h"
class URigVMUserWorkflowOptions;
DECLARE_DELEGATE_ThreeParams(FRigVMReportDelegate, EMessageSeverity::Type, UObject*, const FString&);
// Types of actions within a workflow
UENUM(BlueprintType)
enum class ERigVMUserWorkflowActionType : uint8
{
Invalid = 0 UMETA(Hidden),
SetPinDefaultValue = 1
};
USTRUCT(BlueprintType)
struct RIGVM_API FRigVMUserWorkflowAction
{
GENERATED_BODY()
public:
FORCEINLINE FRigVMUserWorkflowAction()
: Type(ERigVMUserWorkflowActionType::Invalid)
, Subject()
, Data()
{}
FORCEINLINE FRigVMUserWorkflowAction(
ERigVMUserWorkflowActionType InType,
UObject* InSubject,
const FString& InData)
: Type(InType)
, Subject(InSubject)
, Data(InData)
{}
FORCEINLINE bool IsValid() const { return Type != ERigVMUserWorkflowActionType::Invalid && Subject != nullptr; }
FORCEINLINE ERigVMUserWorkflowActionType GetType() const { return Type; }
FORCEINLINE UObject* GetSubject() const { return Subject; }
FORCEINLINE const FString& GetData() const { return Data; }
template<typename T>
FORCEINLINE T* GetSubject() const { return Cast<T>(Subject.Get()); }
protected:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Action, meta = (AllowPrivateAccess = true))
ERigVMUserWorkflowActionType Type;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Action, meta = (AllowPrivateAccess = true))
TObjectPtr<UObject> Subject;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Action, meta = (AllowPrivateAccess = true))
FString Data;
};
// Types of workflows offered by a rigvm struct node
UENUM(BlueprintType)
enum class ERigVMUserWorkflowType : uint8
{
Invalid = 0 UMETA(Hidden),
NodeContext = 0x001,
PinContext = 0x002,
OnPinDefaultChanged = 0x004,
All = NodeContext | PinContext | OnPinDefaultChanged
};
DECLARE_DELEGATE_RetVal_OneParam(TArray<FRigVMUserWorkflowAction>, FRigVMWorkflowGetActionsDelegate, const URigVMUserWorkflowOptions*);
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(TArray<FRigVMUserWorkflowAction>, FRigVMWorkflowGetActionsDynamicDelegate, const URigVMUserWorkflowOptions*, InOptions);
USTRUCT(BlueprintType)
struct RIGVM_API FRigVMUserWorkflow
{
GENERATED_BODY()
public:
FORCEINLINE FRigVMUserWorkflow()
: Title()
, Tooltip()
, Type(ERigVMUserWorkflowType::Invalid)
, OnGetActionsDelegate()
, OnGetActionsDynamicDelegate()
, OptionsClass(nullptr)
{}
FORCEINLINE FRigVMUserWorkflow(
const FString& InTitle,
const FString& InTooltip,
ERigVMUserWorkflowType InType,
FRigVMWorkflowGetActionsDelegate InGetActionsDelegate,
UClass* InOptionsClass)
: Title(InTitle)
, Tooltip(InTooltip)
, Type(InType)
, OnGetActionsDelegate(InGetActionsDelegate)
, OnGetActionsDynamicDelegate()
, OptionsClass(InOptionsClass)
{}
FORCEINLINE virtual ~FRigVMUserWorkflow() {}
FORCEINLINE bool IsValid() const
{
return Type != ERigVMUserWorkflowType::Invalid &&
GetOptionsClass() != nullptr &&
(OnGetActionsDelegate.IsBound() || OnGetActionsDynamicDelegate.IsBound());
}
FORCEINLINE const FString& GetTitle() const { return Title; }
FORCEINLINE const FString& GetTooltip() const { return Tooltip; }
FORCEINLINE ERigVMUserWorkflowType GetType() const { return Type; }
FORCEINLINE UClass* GetOptionsClass() const { return OptionsClass; }
TArray<FRigVMUserWorkflowAction> GetActions(const URigVMUserWorkflowOptions* InOptions) const;
protected:
bool ValidateOptions(const URigVMUserWorkflowOptions* InOptions) const;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Workflow, meta = (AllowPrivateAccess = true))
FString Title;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Workflow, meta = (AllowPrivateAccess = true))
FString Tooltip;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Workflow, meta = (AllowPrivateAccess = true))
ERigVMUserWorkflowType Type;
FRigVMWorkflowGetActionsDelegate OnGetActionsDelegate;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Workflow, meta = (ScriptName = "OnGetActions", AllowPrivateAccess = true))
FRigVMWorkflowGetActionsDynamicDelegate OnGetActionsDynamicDelegate;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Workflow, meta = (AllowPrivateAccess = true))
TObjectPtr<UClass> OptionsClass;
};
UCLASS(BlueprintType)
class RIGVM_API URigVMUserWorkflowOptions : public UObject
{
GENERATED_BODY()
public:
FORCEINLINE bool IsValid() const { return Subject != nullptr; }
bool RequiresDialog() const;
FORCEINLINE UObject* GetSubject() const { return Subject.Get(); }
template<typename T>
FORCEINLINE T* GetSubject() const { return Cast<T>(Subject.Get()); }
FORCEINLINE UObject* GetSubjectChecked() const
{
UObject* Object = Subject.Get();
check(Object);
return Object;
}
template<typename T>
FORCEINLINE T* GetSubjectChecked() const { return CastChecked<T>(Subject.Get()); }
const FRigVMUserWorkflow& GetWorkflow() const { return Workflow; }
void Report(EMessageSeverity::Type InSeverity, const FString& InMessage) const;
template <typename FmtType, typename... Types>
void Reportf(EMessageSeverity::Type InSeverity, const FmtType& Fmt, Types... Args) const
{
Report(InSeverity, FString::Printf(Fmt, Args...));
}
protected:
virtual bool RequiresDialog(const FProperty* InProperty) const;
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = Options, meta = (AllowPrivateAccess = true))
TObjectPtr<UObject> Subject;
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = Options, meta = (AllowPrivateAccess = true))
FRigVMUserWorkflow Workflow;
FRigVMReportDelegate ReportDelegate;
friend class URigVMController;
};