Ok, so I think I am almost there, just missing one key step.
Let us first agree on what my end result should be:
It is basically an AND gate for execution pins – execution is gated until all incoming exec pins fire.
My idea is to track the state of each pin with some boolean array, where arr[i] == true
when the i-th pin executes. I figured a simple way to achieve this is via a SetAndTest UFUNCTION, which set the flag to true, and check whether the condition is met.
// UPARAM(ref) is required otherwise creating the connection will fail -- wrong direction
UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly))
static bool SetAndTestBoolArray_AND(int Idx, UPARAM(ref) TArray<bool>& InOutArray);
bool UFunctionLibrary::SetAndTestBoolArray_AND(int Idx, TArray<bool>& InOutArray)
{
if(!InOutArray.IsValidIndex(Idx))
{
ensure(false); // Check your K2Node implementation
return false;
}
InOutArray[Idx] = true;
for(bool flag : InOutArray)
{
if(!flag)
return false;
}
return true;
}
For this to work, this means each input exec pin in my node should:
-
Connect to a CallFunction node that invokes SetAndTestBoolArray_AND
-
Check the output of the function using a Branch node
-
Connect to the output exec pin if true
I need an array of bools as well, so I use SpawnIntermediateVariable
, in combination with K2Node_AssignmentStatement
and K2Node_MakeArray
to set its value. Note, this might be redundant – I should be able to pass the output of MakeArray to all my CallFunction nodes, but let’s stick with this for now.
Resulting ExpandNode
function:
void UK2Node_MultiAND::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);
UEdGraphSchema const* schema = SourceGraph->GetSchema();
// Create a boolean array, where arr[i] = true if the i-th exec pin was fired.
// Assign its value from the MakeArray node
UK2Node_TemporaryVariable* execFlagArr = CompilerContext.SpawnInternalVariable(this, UEdGraphSchema_K2::PC_Boolean, NAME_None, nullptr, EPinContainerType::Array);
UK2Node_AssignmentStatement* assignArrNode = CompilerContext.SpawnIntermediateNode<UK2Node_AssignmentStatement>(this);
UK2Node_MakeArray* makeArrNode = CompilerContext.SpawnIntermediateNode<UK2Node_MakeArray>(this);
assignArrNode->AllocateDefaultPins();
makeArrNode->NumInputs = 0;
makeArrNode->AllocateDefaultPins();
// Boolean flags matching the number of exec inputs
for(int i = 0; i < NumInputs; ++i)
{
makeArrNode->AddInputPin();
makeArrNode->GetPinWithDirectionAt(i, EEdGraphPinDirection::EGPD_Input)->DefaultValue = "false";
}
bool success = true;
// Perform the "assignment" of the variable.
success &= schema->TryCreateConnection(makeArrNode->GetOutputPin(), assignArrNode->GetValuePin());
success &= schema->TryCreateConnection(execFlagArr->GetVariablePin(), assignArrNode->GetVariablePin());
// Now create a node for our function that sets an idx into the bool arr, and return true if all flags are set
// We need a separate function per index!
for(int i = 0; i < NumInputs; ++i)
{
// Each exec pin will set its respective index in the boolean array, then check if the condition is met to proceed output execution
UK2Node_CallFunction* setterFunctionNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this);
setterFunctionNode->SetFromFunction(UXStudioStatics::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UXStudioStatics, SetAndTestBoolArray_AND)));
setterFunctionNode->AllocateDefaultPins();
setterFunctionNode->FindPinChecked(Priv::locSetterFuncIndexPinName, EEdGraphPinDirection::EGPD_Input)->DefaultValue = FString::FromInt(i);
// Pass the array by ref to the function arg
success &= schema->TryCreateConnection(execFlagArr->GetVariablePin(), setterFunctionNode->FindPinChecked(Priv::locSetterFuncArrayPinName));
// Check the return of the function - if true, then all exec pins have fired.
UK2Node_IfThenElse* branchNode = CompilerContext.SpawnIntermediateNode<UK2Node_IfThenElse>(this);
branchNode->AllocateDefaultPins();
success &= schema->TryCreateConnection(setterFunctionNode->GetReturnValuePin(), branchNode->GetConditionPin());
// Connect the i-th Exec pin to the setter function. The resulting Then pin goes to the branch logic.
success &= CompilerContext.MovePinLinksToIntermediate(*GetExecPin(i), *setterFunctionNode->GetExecPin()).CanSafeConnect();
success &= CompilerContext.MovePinLinksToIntermediate(*branchNode->GetThenPin(), *GetThenPin()).CanSafeConnect();
}
if(!success)
{
CompilerContext.MessageLog.Error(TEXT("Node @@ compilation failed"), this);
}
// Is this correct?
BreakAllNodeLinks();
}
Everything compiles, and the function gets called as expected. In the first invocation, one flag is set to true. Then when the second pin fires, and the function is invoked a second time, the boolean array is reset, with all values being false. If I can understand why the array does not persist, this problem would be solved. Maybe it’s the difference between using the Assignment node vs th Set Variable node?