842 lines
24 KiB
C++
842 lines
24 KiB
C++
#include "WingVariables.h"
|
|
#include "WingTypes.h"
|
|
#include "WingUtils.h"
|
|
#include "WingProperty.h"
|
|
#include "WingTokenizer.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "K2Node_FunctionEntry.h"
|
|
#include "K2Node_FunctionResult.h"
|
|
#include "K2Node_CustomEvent.h"
|
|
#include "K2Node_EditablePinBase.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Kismet2/KismetEditorUtilities.h"
|
|
|
|
// Flag names used for blueprint variables.
|
|
static const FName Flag_InstanceEditable(TEXT("InstanceEditable"));
|
|
static const FName Flag_BlueprintReadOnly(TEXT("BlueprintReadOnly"));
|
|
static const FName Flag_ExposeOnSpawn(TEXT("ExposeOnSpawn"));
|
|
static const FName Flag_Private(TEXT("Private"));
|
|
static const FName Flag_ExposeToCinematics(TEXT("ExposeToCinematics"));
|
|
|
|
|
|
static TSet<FName> Flags_None = { };
|
|
static TSet<FName> Flags_BlueprintVariables = { Flag_InstanceEditable, Flag_BlueprintReadOnly, Flag_ExposeOnSpawn, Flag_Private, Flag_ExposeToCinematics };
|
|
|
|
|
|
|
|
void WingVariableList::Print(WingOut Out)
|
|
{
|
|
if (Variables.IsEmpty()) return;
|
|
Out.Print(ListName);
|
|
Out.PrintChar(':');
|
|
Out.PrintChar('\n');
|
|
for (const Var& V : Variables)
|
|
{
|
|
FString TypeStr = UWingTypes::TypeToText(V.Type);
|
|
FString NameStr = WingUtils::ExternalizeID(V.Name);
|
|
|
|
// Build flags string.
|
|
FString FlagsStr;
|
|
if (V.Flags.Num() > 0)
|
|
{
|
|
TArray<FName> Sorted = V.Flags.Array();
|
|
Sorted.Sort(FNameLexicalLess());
|
|
for (const FName& Flag : Sorted)
|
|
{
|
|
if (!FlagsStr.IsEmpty()) FlagsStr += TEXT(", ");
|
|
FlagsStr += Flag.ToString();
|
|
}
|
|
}
|
|
|
|
// Print: type name (flags) = defaultvalue
|
|
Out.Printf(TEXT(" %s %s"), *TypeStr, *NameStr);
|
|
if (!FlagsStr.IsEmpty())
|
|
Out.Printf(TEXT(" (%s)"), *FlagsStr);
|
|
if (!V.DefaultValue.IsEmpty())
|
|
Out.Printf(TEXT(" = %s"), *V.DefaultValue);
|
|
Out.Print(TEXT("\n"));
|
|
}
|
|
}
|
|
|
|
void WingVariableList::PrintCompact(WingOut Out)
|
|
{
|
|
bool First = true;
|
|
for (const Var& V : Variables)
|
|
{
|
|
if (!First) Out.PrintChar(',');
|
|
First = false;
|
|
Out.Printf(TEXT("%s %s"), *UWingTypes::TypeToText(V.Type), *WingUtils::ExternalizeID(V.Name));
|
|
}
|
|
}
|
|
|
|
void WingVariableList::ClearLinks()
|
|
{
|
|
for (Var &V : Variables)
|
|
{
|
|
V.BPVar = nullptr;
|
|
V.Pin = nullptr;
|
|
}
|
|
}
|
|
|
|
bool WingVariableList::CheckSanity(const TSet<FName> &GoodFlags, bool Allow, WingOut Errors)
|
|
{
|
|
if ((!Allow) && (!Variables.IsEmpty()))
|
|
{
|
|
Errors.Printf(TEXT("In this context, %s must be empty."), ListName);
|
|
return false;
|
|
}
|
|
for (const Var &Variable : Variables)
|
|
{
|
|
FString VarName = WingUtils::ExternalizeID(Variable.Name);
|
|
FString TypeText = UWingTypes::TypeToText(Variable.Type);
|
|
if (TypeText.IsEmpty())
|
|
{
|
|
Errors.Printf(TEXT("Type of variable %s is not valid for unknown reasons\n"), *VarName);
|
|
return false;
|
|
}
|
|
if (!UWingTypes::IsBlueprintType(Variable.Type))
|
|
{
|
|
Errors.Printf(TEXT("Type is not a valid BlueprintType: %s %s\n"), *TypeText, *VarName);
|
|
return false;
|
|
}
|
|
for (FName Flag : Variable.Flags)
|
|
{
|
|
if (!GoodFlags.Contains(Flag))
|
|
{
|
|
Errors.Printf(TEXT("Flag %s is not valid here. Valid flags are:"), *Flag.ToString());
|
|
for (FName Rel : GoodFlags) Errors.Printf(TEXT(" %s\n"), *Rel.ToString());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WingVariableList::ParseString(const FString &Input, WingOut Errors)
|
|
{
|
|
Variables.Empty();
|
|
|
|
TArray<FString> Lines;
|
|
Input.ParseIntoArrayLines(Lines);
|
|
|
|
for (const FString& Line : Lines)
|
|
{
|
|
WingTokenizer Tok(Line);
|
|
if (Tok.NextType() == 0) continue;
|
|
Var V;
|
|
V.DefaultSpecified = false;
|
|
if (!ParseOneVariable(Tok, V, Errors)) return false;
|
|
Variables.Add(MoveTemp(V));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WingVariableList::ParseNamesString(const FString &Input, WingOut Errors)
|
|
{
|
|
Variables.Empty();
|
|
WingTokenizer Tok(Input);
|
|
while (Tok.TokenIs(Tok.Identifier))
|
|
{
|
|
FName Name = Tok.NextName();
|
|
Var V;
|
|
V.Name = Name;
|
|
Variables.Add(V);
|
|
V.DefaultSpecified = false;
|
|
Tok.Advance();
|
|
if (Tok.TokenIs(',')) Tok.Advance();
|
|
}
|
|
if (!Tok.TokenIs(0))
|
|
{
|
|
Tok.SaveCursor(NAME_None);
|
|
Errors.Printf(TEXT("Unexpected token %s in variable list"),
|
|
*FString(Tok.GetRange(NAME_None, 1)));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WingVariableList::ParseOneVariable(WingTokenizer &Tok, Var &V, WingOut Errors)
|
|
{
|
|
// Parse type.
|
|
UWingTypes::Requirements Req;
|
|
Req.BlueprintType = true;
|
|
Req.Blueprintable = false;
|
|
Req.AllowContainer = true;
|
|
if (!UWingTypes::TextToType(Tok, V.Type, Req, false, Errors))
|
|
return false;
|
|
|
|
// Parse name.
|
|
if (Tok.NextType() != Tok.Identifier)
|
|
{
|
|
Errors.Print(TEXT("ERROR: Expected variable name after type\n"));
|
|
return false;
|
|
}
|
|
V.Name = Tok.NextName();
|
|
Tok.Advance();
|
|
|
|
// Parse optional flags: (flag1, flag2, ...)
|
|
if (Tok.TokenIs('('))
|
|
{
|
|
if (!ParseVariableFlags(Tok, V.Flags, Errors)) return false;
|
|
}
|
|
|
|
// Parse optional default value: = rest-of-line
|
|
if (Tok.NextType() == Tok.RestOfLine)
|
|
{
|
|
V.DefaultSpecified = true;
|
|
V.DefaultValue = FString(Tok.NextRest().TrimStartAndEnd());
|
|
Tok.Advance();
|
|
}
|
|
|
|
// Should be at end of line.
|
|
if (Tok.NextType() != 0)
|
|
{
|
|
Tok.SaveCursor(NAME_None);
|
|
Errors.Printf(TEXT("ERROR: Unexpected token after variable declaration: '%s'\n"),
|
|
*FString(Tok.GetRange(NAME_None, 1)));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WingVariableList::ParseVariableFlags(WingTokenizer &Tok, TSet<FName> &Out, WingOut Errors)
|
|
{
|
|
Tok.Advance(); // Step over open-paren
|
|
while (Tok.TokenIs(Tok.Identifier))
|
|
{
|
|
Out.Add(Tok.NextName());
|
|
Tok.Advance();
|
|
// Commas are optional.
|
|
if (Tok.TokenIs(',')) Tok.Advance();
|
|
}
|
|
if (!Tok.TokenIs(')'))
|
|
{
|
|
Tok.SaveCursor(NAME_None);
|
|
Errors.Printf(TEXT("ERROR: flag list contains invalid token '%s'\n"),
|
|
*FString(Tok.GetRange(NAME_None, 1)));
|
|
return false;
|
|
}
|
|
Tok.Advance(); // Step over close-paren
|
|
return true;
|
|
}
|
|
|
|
void WingVariables::Empty()
|
|
{
|
|
BlueprintVariables.Empty();
|
|
LocalVariables.Empty();
|
|
InputVariables.Empty();
|
|
OutputVariables.Empty();
|
|
}
|
|
|
|
bool WingVariables::IsEmpty()
|
|
{
|
|
return (BlueprintVariables.IsEmpty() &&
|
|
LocalVariables.IsEmpty() &&
|
|
InputVariables.IsEmpty() &&
|
|
OutputVariables.IsEmpty());
|
|
}
|
|
|
|
void WingVariables::ClearLinks()
|
|
{
|
|
BlueprintVariables.ClearLinks();
|
|
LocalVariables.ClearLinks();
|
|
InputVariables.ClearLinks();
|
|
OutputVariables.ClearLinks();
|
|
}
|
|
|
|
void WingVariables::Print(WingOut Out)
|
|
{
|
|
BlueprintVariables.Print(Out);
|
|
LocalVariables.Print(Out);
|
|
InputVariables.Print(Out);
|
|
OutputVariables.Print(Out);
|
|
}
|
|
|
|
void WingVariables::Load(WingOut Errors)
|
|
{
|
|
Empty();
|
|
if (Blueprint) return LoadBlueprint();
|
|
if (Graph) return LoadGraph();
|
|
if (CustomEvent) return LoadCustomEvent();
|
|
ErrorNoBackingStore(Errors);
|
|
}
|
|
|
|
void WingVariables::LoadBlueprint()
|
|
{
|
|
UObject *CDO = WingUtils::GetGeneratedCDO(Blueprint.Get());
|
|
|
|
for (FBPVariableDescription& Desc : Blueprint->NewVariables)
|
|
{
|
|
// Skip event dispatchers.
|
|
if (Desc.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue;
|
|
// Load up the variable.
|
|
Var V = LoadBlueprintVariableDescription(Desc, CDO);
|
|
BlueprintVariables.Variables.Add(MoveTemp(V));
|
|
}
|
|
}
|
|
|
|
void WingVariables::LoadGraph()
|
|
{
|
|
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
|
|
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode;
|
|
FBlueprintEditorUtils::GetEntryAndResultNodes(Graph.Get(), EntryNode, ResultNode);
|
|
if (EntryNode.IsValid()) LoadEditablePinBase(EntryNode.Get(), InputVariables);
|
|
if (ResultNode.IsValid()) LoadEditablePinBase(ResultNode.Get(), OutputVariables);
|
|
LoadLocalVariables(EntryNode.Get());
|
|
}
|
|
|
|
void WingVariables::LoadLocalVariables(UK2Node_EditablePinBase *Node)
|
|
{
|
|
UK2Node_FunctionEntry *Func = Cast<UK2Node_FunctionEntry>(Node);
|
|
if (Func == nullptr) return;
|
|
for (FBPVariableDescription& Desc : Func->LocalVariables)
|
|
{
|
|
Var V = LoadLocalVariableDescription(Desc);
|
|
LocalVariables.Variables.Add(MoveTemp(V));
|
|
}
|
|
}
|
|
|
|
WingVariables::Var WingVariables::LoadBlueprintVariableDescription(FBPVariableDescription &Desc, UObject *CDO)
|
|
{
|
|
Var Result;
|
|
Result.Name = Desc.VarName;
|
|
Result.Type = Desc.VarType;
|
|
|
|
if (!(Desc.PropertyFlags & CPF_DisableEditOnInstance))
|
|
Result.Flags.Add(Flag_InstanceEditable);
|
|
|
|
if (Desc.PropertyFlags & CPF_BlueprintReadOnly)
|
|
Result.Flags.Add(Flag_BlueprintReadOnly);
|
|
|
|
if (Desc.PropertyFlags & CPF_Interp)
|
|
Result.Flags.Add(Flag_ExposeToCinematics);
|
|
|
|
if (Desc.HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn))
|
|
Result.Flags.Add(Flag_ExposeOnSpawn);
|
|
|
|
if (Desc.HasMetaData(FBlueprintMetadata::MD_Private))
|
|
Result.Flags.Add(Flag_Private);
|
|
|
|
// Read default value from CDO if available.
|
|
if (CDO)
|
|
{
|
|
FProperty* Prop = CDO->GetClass()->FindPropertyByName(Desc.VarName);
|
|
if (Prop)
|
|
{
|
|
Result.DefaultValue = FWingProperty(Prop, CDO, false).GetText();
|
|
Result.DefaultSpecified = true;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
WingVariables::Var WingVariables::LoadLocalVariableDescription(FBPVariableDescription &Desc)
|
|
{
|
|
Var Result;
|
|
Result.Name = Desc.VarName;
|
|
Result.Type = Desc.VarType;
|
|
Result.DefaultValue = Desc.DefaultValue;
|
|
Result.DefaultSpecified = true;
|
|
return Result;
|
|
}
|
|
|
|
void WingVariables::LoadEditablePinBase(UK2Node_EditablePinBase* Node, WingVariableList &List)
|
|
{
|
|
if (Node == nullptr) return;
|
|
for (const TSharedPtr<FUserPinInfo>& PinInfo : Node->UserDefinedPins)
|
|
{
|
|
Var V;
|
|
V.Name = PinInfo->PinName;
|
|
V.Type = PinInfo->PinType;
|
|
V.DefaultValue = PinInfo->PinDefaultValue;
|
|
V.DefaultSpecified = true;
|
|
List.Variables.Add(MoveTemp(V));
|
|
}
|
|
}
|
|
|
|
void WingVariables::LoadCustomEvent()
|
|
{
|
|
LoadEditablePinBase(CustomEvent.Get(), InputVariables);
|
|
}
|
|
|
|
bool WingVariables::Check(WingOut Errors)
|
|
{
|
|
if (Blueprint) return CheckBlueprint(Errors);
|
|
if (Graph) return CheckGraph(Errors);
|
|
if (CustomEvent) return CheckCustomEvent(Errors);
|
|
return ErrorNoBackingStore(Errors);
|
|
}
|
|
|
|
bool WingVariables::CheckBlueprint(WingOut Errors)
|
|
{
|
|
bool OK = true;
|
|
if (!BlueprintVariables.CheckSanity(Flags_BlueprintVariables, true, Errors)) OK = false;
|
|
if (!LocalVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
|
|
if (!InputVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
|
|
if (!OutputVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
|
|
return OK;
|
|
}
|
|
|
|
bool WingVariables::CheckGraph(WingOut Errors)
|
|
{
|
|
EGraphType T = Graph->GetSchema()->GetGraphType(Graph.Get());
|
|
bool AllowLocal = (T == EGraphType::GT_Function);
|
|
bool AllowInput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro);
|
|
bool AllowOutput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro);
|
|
if ((!AllowLocal) && (!AllowInput) && (!AllowOutput))
|
|
{
|
|
Errors.Printf(TEXT("This graph type has no variables."));
|
|
return false;
|
|
}
|
|
|
|
bool OK = true;
|
|
if (!BlueprintVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
|
|
if (!LocalVariables.CheckSanity(Flags_None, AllowLocal, Errors)) OK = false;
|
|
if (!InputVariables.CheckSanity(Flags_None, AllowInput, Errors)) OK = false;
|
|
if (!OutputVariables.CheckSanity(Flags_None, AllowOutput, Errors)) OK = false;
|
|
return OK;
|
|
}
|
|
|
|
bool WingVariables::CheckCustomEvent(WingOut Errors)
|
|
{
|
|
bool OK = true;
|
|
if (!BlueprintVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
|
|
if (!LocalVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
|
|
if (!InputVariables.CheckSanity(Flags_None, true, Errors)) OK = false;
|
|
if (!OutputVariables.CheckSanity(Flags_None, false, Errors)) OK = false;
|
|
return OK;
|
|
}
|
|
|
|
bool WingVariables::Modify(WingOut Errors)
|
|
{
|
|
if (Blueprint) return ModifyBlueprint(Errors);
|
|
if (Graph) return ModifyGraph(Errors);
|
|
if (CustomEvent) return ModifyCustomEvent(Errors);
|
|
return ErrorNoBackingStore(Errors);
|
|
}
|
|
|
|
bool WingVariables::ModifyBlueprint(WingOut Errors)
|
|
{
|
|
if (!CheckBlueprint(Errors)) return false;
|
|
if (!LinkBlueprintVariables(Errors)) return false;
|
|
for (Var &V : BlueprintVariables.Variables)
|
|
{
|
|
V.BPVar->VarType = V.Type;
|
|
ModifyBlueprintVariableFlags(V);
|
|
}
|
|
if (!ModifyBlueprintDefaults(Errors)) return false;
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::LinkBlueprintVariables(WingOut Errors)
|
|
{
|
|
ClearLinks();
|
|
for (Var &V : BlueprintVariables.Variables)
|
|
{
|
|
V.BPVar = WingUtils::FindOneWithInternalID(
|
|
V.Name, Blueprint->NewVariables, TEXT("non-inherited variable"), Errors);
|
|
if (V.BPVar == nullptr) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WingVariables::ModifyBlueprintVariableFlags(Var &Input)
|
|
{
|
|
FBPVariableDescription &Out = *Input.BPVar;
|
|
|
|
// InstanceEditable: absence means CPF_DisableEditOnInstance is set
|
|
if (Input.Flags.Contains(Flag_InstanceEditable))
|
|
Out.PropertyFlags &= ~CPF_DisableEditOnInstance;
|
|
else
|
|
Out.PropertyFlags |= CPF_DisableEditOnInstance;
|
|
|
|
if (Input.Flags.Contains(Flag_BlueprintReadOnly))
|
|
Out.PropertyFlags |= CPF_BlueprintReadOnly;
|
|
else
|
|
Out.PropertyFlags &= ~CPF_BlueprintReadOnly;
|
|
|
|
if (Input.Flags.Contains(Flag_ExposeToCinematics))
|
|
Out.PropertyFlags |= CPF_Interp;
|
|
else
|
|
Out.PropertyFlags &= ~CPF_Interp;
|
|
|
|
if (Input.Flags.Contains(Flag_ExposeOnSpawn))
|
|
Out.SetMetaData(FBlueprintMetadata::MD_ExposeOnSpawn, TEXT("true"));
|
|
else
|
|
Out.RemoveMetaData(FBlueprintMetadata::MD_ExposeOnSpawn);
|
|
|
|
if (Input.Flags.Contains(Flag_Private))
|
|
Out.SetMetaData(FBlueprintMetadata::MD_Private, TEXT("true"));
|
|
else
|
|
Out.RemoveMetaData(FBlueprintMetadata::MD_Private);
|
|
}
|
|
|
|
bool WingVariables::ModifyBlueprintDefaults(WingOut Errors)
|
|
{
|
|
bool AnySpecified = false;
|
|
for (Var &Input : BlueprintVariables.Variables)
|
|
if (Input.DefaultSpecified) AnySpecified = true;
|
|
if (!AnySpecified) return true;
|
|
|
|
FKismetEditorUtilities::CompileBlueprint(Blueprint.Get());
|
|
UObject *CDO = WingUtils::GetGeneratedCDO(Blueprint.Get());
|
|
if (!CDO)
|
|
{
|
|
Errors.Printf(TEXT("Blueprint didn't compile, cannot store default values"));
|
|
return false;
|
|
}
|
|
|
|
for (Var &Input : BlueprintVariables.Variables)
|
|
{
|
|
if (Input.DefaultSpecified)
|
|
{
|
|
FProperty* Prop = CDO->GetClass()->FindPropertyByName(Input.Name);
|
|
if (!Prop)
|
|
{
|
|
Errors.Printf(TEXT("Variable exists in blueprint, but not the generated class, which is weird: %s."),
|
|
*WingTokenizer::ExternalizeID(Input.Name));
|
|
return false;
|
|
}
|
|
if (!FWingProperty(Prop, CDO, true).SetText(Input.DefaultValue, Errors)) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::ModifyGraph(WingOut Errors)
|
|
{
|
|
if (!CheckGraph(Errors)) return false;
|
|
ClearLinks();
|
|
|
|
UK2Node_EditablePinBase *InputNode, *OutputNode;
|
|
UK2Node_FunctionEntry *LocalNode;
|
|
bool CreateOutputNode = !OutputVariables.Variables.IsEmpty();
|
|
if (!GetGraphNodes(InputNode, OutputNode, LocalNode, CreateOutputNode, Errors)) return false;
|
|
|
|
for (Var &V : LocalVariables.Variables)
|
|
{
|
|
FBPVariableDescription *Desc =
|
|
WingUtils::FindOneWithInternalID(V.Name, LocalNode->LocalVariables, TEXT("local variable"), Errors);
|
|
if (Desc == nullptr) return false;
|
|
Desc->VarType = V.Type;
|
|
if (V.DefaultSpecified) Desc->DefaultValue = V.DefaultValue;
|
|
}
|
|
if (!ModifyEditablePinBase(InputVariables, InputNode, Errors)) return false;
|
|
if (OutputNode)
|
|
if (!ModifyEditablePinBase(OutputVariables, OutputNode, Errors)) return false;
|
|
|
|
if (InputNode) InputNode->ReconstructNode();
|
|
if (OutputNode) OutputNode->ReconstructNode();
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::ModifyCustomEvent(WingOut Errors)
|
|
{
|
|
if (!CheckCustomEvent(Errors)) return false;
|
|
ClearLinks();
|
|
if (!ModifyEditablePinBase(InputVariables, CustomEvent.Get(), Errors)) return false;
|
|
CustomEvent->ReconstructNode();
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::ModifyEditablePinBase(WingVariableList &List, UK2Node_EditablePinBase *Node, WingOut Errors)
|
|
{
|
|
for (Var &V : List.Variables)
|
|
{
|
|
TSharedPtr<FUserPinInfo> Found =
|
|
WingUtils::FindOneWithInternalID(V.Name, Node->UserDefinedPins, List.ListName, Errors);
|
|
if (!Found) return false;
|
|
Found->PinType = V.Type;
|
|
if (V.DefaultSpecified) Found->PinDefaultValue = V.DefaultValue;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::GetGraphNodes(
|
|
UK2Node_EditablePinBase *&InputNode,
|
|
UK2Node_EditablePinBase *&OutputNode,
|
|
UK2Node_FunctionEntry *&LocalNode,
|
|
bool CreateOutputNode,
|
|
WingOut Errors)
|
|
{
|
|
// In theory, none of these errors should trigger, because
|
|
// we call CheckSanity before calling this function. But
|
|
// you never know.
|
|
InputNode = nullptr;
|
|
OutputNode = nullptr;
|
|
LocalNode = nullptr;
|
|
|
|
TWeakObjectPtr<UK2Node_EditablePinBase> Inputs, Outputs;
|
|
FBlueprintEditorUtils::GetEntryAndResultNodes(Graph.Get(), Inputs, Outputs);
|
|
if (!Inputs.IsValid())
|
|
{
|
|
Errors.Printf(TEXT("ERROR: no function entry node for graph."));
|
|
return false;
|
|
}
|
|
if (!Outputs.IsValid() && CreateOutputNode)
|
|
{
|
|
Outputs = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(Inputs.Get());
|
|
if (!Outputs.IsValid())
|
|
{
|
|
Errors.Printf(TEXT("ERROR: couldn't create result node for graph."));
|
|
return false;
|
|
}
|
|
}
|
|
UK2Node_FunctionEntry *Locals = Cast<UK2Node_FunctionEntry>(Inputs.Get());
|
|
if (!Locals && (!LocalVariables.Variables.IsEmpty()))
|
|
{
|
|
Errors.Printf(TEXT("ERROR: function doesn't have a proper entry node for local variables"));
|
|
return false;
|
|
}
|
|
InputNode = Inputs.Get();
|
|
OutputNode = Outputs.Get();
|
|
LocalNode = Locals;
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::Create(WingOut Errors)
|
|
{
|
|
if (Blueprint) return CreateBlueprint(Errors);
|
|
if (Graph) return CreateGraph(Errors);
|
|
if (CustomEvent) return CreateCustomEvent(Errors);
|
|
return ErrorNoBackingStore(Errors);
|
|
}
|
|
|
|
bool WingVariables::CreateBlueprint(WingOut Errors)
|
|
{
|
|
if (!CheckBlueprint(Errors)) return false;
|
|
ClearLinks();
|
|
|
|
// Check for name collisions against existing variables, components, and the like.
|
|
TSet<FName> Names;
|
|
FBlueprintEditorUtils::GetClassVariableList(Blueprint.Get(), Names);
|
|
if (!WingUtils::FindNoDuplicateNames(Names, BlueprintVariables.Variables, TEXT("variable or component"), Errors)) return false;
|
|
|
|
// Create the variables.
|
|
for (const WingVariables::Var& V : BlueprintVariables.Variables)
|
|
{
|
|
if (!FBlueprintEditorUtils::AddMemberVariable(Blueprint.Get(), V.Name, V.Type))
|
|
{
|
|
Errors.Printf(TEXT("ERROR: Failed to add variable '%s'\n"),
|
|
*WingUtils::ExternalizeID(V.Name));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!LinkBlueprintVariables(Errors)) return false;
|
|
for (Var &V : BlueprintVariables.Variables) ModifyBlueprintVariableFlags(V);
|
|
if (!ModifyBlueprintDefaults(Errors)) return false;
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::CreateGraph(WingOut Errors)
|
|
{
|
|
if (!CheckGraph(Errors)) return false;
|
|
ClearLinks();
|
|
|
|
UK2Node_EditablePinBase *InputNode, *OutputNode;
|
|
UK2Node_FunctionEntry *LocalNode;
|
|
bool CreateOutputNode = (!OutputVariables.Variables.IsEmpty());
|
|
if (!GetGraphNodes(InputNode, OutputNode, LocalNode, CreateOutputNode, Errors)) return false;
|
|
|
|
// Check for name collisions against existing local variables.
|
|
const TCHAR *ctx = TEXT("local, function input, and function output variables");
|
|
TSet<FName> Names;
|
|
if (InputNode && !WingUtils::FindNoDuplicateNames(
|
|
Names, InputNode->UserDefinedPins, ctx, Errors)) return false;
|
|
if (OutputNode && !WingUtils::FindNoDuplicateNames(
|
|
Names, OutputNode->UserDefinedPins, ctx, Errors)) return false;
|
|
if (LocalNode && !WingUtils::FindNoDuplicateNames(
|
|
Names, LocalNode->LocalVariables, ctx, Errors)) return false;
|
|
if (!WingUtils::FindNoDuplicateNames(
|
|
Names, InputVariables.Variables, ctx, Errors)) return false;
|
|
if (!WingUtils::FindNoDuplicateNames(
|
|
Names, OutputVariables.Variables, ctx, Errors)) return false;
|
|
if (!WingUtils::FindNoDuplicateNames(
|
|
Names, LocalVariables.Variables, ctx, Errors)) return false;
|
|
|
|
// Create input pins on the input node.
|
|
for (const Var& V : InputVariables.Variables)
|
|
AddUserPinInfo(V, EGPD_Output, InputNode);
|
|
|
|
// Create output pins on the output node.
|
|
for (const Var& V : OutputVariables.Variables)
|
|
AddUserPinInfo(V, EGPD_Input, OutputNode);
|
|
|
|
// Create local variables via the proper API.
|
|
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph.Get());
|
|
for (const Var& V : LocalVariables.Variables)
|
|
{
|
|
if (!FBlueprintEditorUtils::AddLocalVariable(BP, Graph.Get(), V.Name, V.Type, V.DefaultValue))
|
|
{
|
|
Errors.Printf(TEXT("ERROR: Failed to create local variable '%s'\n"),
|
|
*WingUtils::ExternalizeID(V.Name));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (InputNode) InputNode->ReconstructNode();
|
|
if (OutputNode) OutputNode->ReconstructNode();
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::CreateCustomEvent(WingOut Errors)
|
|
{
|
|
if (!CheckCustomEvent(Errors)) return false;
|
|
ClearLinks();
|
|
|
|
// Check for name collisions against existing pins.
|
|
TSet<FName> Names;
|
|
if (!WingUtils::FindNoDuplicateNames(
|
|
Names, CustomEvent->UserDefinedPins, TEXT("event parameter"), Errors)) return false;
|
|
if (!WingUtils::FindNoDuplicateNames(
|
|
Names, InputVariables.Variables, TEXT("event parameter"), Errors)) return false;
|
|
|
|
for (const Var& V : InputVariables.Variables)
|
|
AddUserPinInfo(V, EGPD_Output, CustomEvent.Get());
|
|
|
|
CustomEvent->ReconstructNode();
|
|
return true;
|
|
}
|
|
|
|
void WingVariables::AddUserPinInfo(const Var &V, EEdGraphPinDirection Dir, UK2Node_EditablePinBase *Node)
|
|
{
|
|
TSharedPtr<FUserPinInfo> Result = MakeShareable( new FUserPinInfo() );
|
|
Result->PinName = V.Name;
|
|
Result->PinType = V.Type;
|
|
Result->PinDefaultValue = V.DefaultValue;
|
|
Result->DesiredPinDirection = Dir;
|
|
Node->UserDefinedPins.Add(Result);
|
|
}
|
|
|
|
bool WingVariables::Remove(WingOut Errors)
|
|
{
|
|
if (Blueprint) return RemoveBlueprint(Errors);
|
|
if (Graph) return RemoveGraph(Errors);
|
|
if (CustomEvent) return RemoveCustomEvent(Errors);
|
|
return ErrorNoBackingStore(Errors);
|
|
}
|
|
|
|
bool WingVariables::RemoveBlueprint(WingOut Errors)
|
|
{
|
|
// Verify that all named variables exist.
|
|
TArray<FName> Names;
|
|
for (const Var& V : BlueprintVariables.Variables)
|
|
{
|
|
FBPVariableDescription* Found =
|
|
WingUtils::FindOneWithInternalID(V.Name, Blueprint->NewVariables, TEXT("non-inherited variable"), Errors);
|
|
if (!Found) return false;
|
|
Names.Add(V.Name);
|
|
}
|
|
|
|
// Remove them.
|
|
FBlueprintEditorUtils::BulkRemoveMemberVariables(Blueprint.Get(), Names);
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::RemoveGraph(WingOut Errors)
|
|
{
|
|
UK2Node_EditablePinBase *InputNode, *OutputNode;
|
|
UK2Node_FunctionEntry *LocalNode;
|
|
if (!GetGraphNodes(InputNode, OutputNode, LocalNode, false, Errors)) return false;
|
|
|
|
// Verify that all named variables exist before removing anything.
|
|
if (!OutputNode && (!OutputVariables.Variables.IsEmpty()))
|
|
{
|
|
Errors.Printf(TEXT("No output variables on this function to remove.\n"));
|
|
return false;
|
|
}
|
|
for (const Var& V : InputVariables.Variables)
|
|
{
|
|
TSharedPtr<FUserPinInfo> Found =
|
|
WingUtils::FindOneWithInternalID(V.Name, InputNode->UserDefinedPins, TEXT("input variable"), Errors);
|
|
if (!Found) return false;
|
|
}
|
|
for (const Var& V : OutputVariables.Variables)
|
|
{
|
|
TSharedPtr<FUserPinInfo> Found =
|
|
WingUtils::FindOneWithInternalID(V.Name, OutputNode->UserDefinedPins, TEXT("output variable"), Errors);
|
|
if (!Found) return false;
|
|
}
|
|
for (const Var& V : LocalVariables.Variables)
|
|
{
|
|
FBPVariableDescription* Found =
|
|
WingUtils::FindOneWithInternalID(V.Name, LocalNode->LocalVariables, TEXT("local variable"), Errors);
|
|
if (!Found) return false;
|
|
}
|
|
|
|
// Remove input and output pins.
|
|
for (const Var& V : InputVariables.Variables)
|
|
InputNode->RemoveUserDefinedPinByName(V.Name);
|
|
for (const Var& V : OutputVariables.Variables)
|
|
OutputNode->RemoveUserDefinedPinByName(V.Name);
|
|
|
|
// Remove local variables.
|
|
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph.Get());
|
|
for (const Var& V : LocalVariables.Variables)
|
|
{
|
|
LocalNode->LocalVariables.RemoveAll(
|
|
[&](const FBPVariableDescription& Desc) { return Desc.VarName == V.Name; });
|
|
FBlueprintEditorUtils::RemoveVariableNodes(BP, V.Name, true, Graph.Get());
|
|
}
|
|
|
|
if (InputNode) InputNode->ReconstructNode();
|
|
if (OutputNode) OutputNode->ReconstructNode();
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::RemoveCustomEvent(WingOut Errors)
|
|
{
|
|
// Verify that all named parameters exist before removing anything.
|
|
for (const Var& V : InputVariables.Variables)
|
|
{
|
|
TSharedPtr<FUserPinInfo> Found =
|
|
WingUtils::FindOneWithInternalID(V.Name, CustomEvent->UserDefinedPins, TEXT("event parameter"), Errors);
|
|
if (!Found) return false;
|
|
}
|
|
|
|
for (const Var& V : InputVariables.Variables)
|
|
CustomEvent->RemoveUserDefinedPinByName(V.Name);
|
|
|
|
CustomEvent->ReconstructNode();
|
|
return true;
|
|
}
|
|
|
|
bool WingVariables::SetBackingStore(UObject *Obj, WingOut Errors)
|
|
{
|
|
Blueprint.Reset();
|
|
Graph.Reset();
|
|
CustomEvent.Reset();
|
|
if (UBlueprint *BP = Cast<UBlueprint>(Obj))
|
|
{
|
|
Blueprint.Reset(BP);
|
|
return true;
|
|
}
|
|
if (UEdGraph *G = Cast<UEdGraph>(Obj))
|
|
{
|
|
Graph.Reset(G);
|
|
return true;
|
|
}
|
|
if (UK2Node_CustomEvent *E = Cast<UK2Node_CustomEvent>(Obj))
|
|
{
|
|
CustomEvent.Reset(E);
|
|
return true;
|
|
}
|
|
Errors.Printf(TEXT(
|
|
"ERROR: The variable editor can only edit blueprints, "
|
|
"graphs, and custom event nodes. Passed in: %s"),
|
|
*WingUtils::FormatName(Obj->GetClass()));
|
|
return false;
|
|
}
|
|
|
|
bool WingVariables::ErrorNoBackingStore(WingOut Errors)
|
|
{
|
|
Errors.Printf(TEXT(
|
|
"ERROR: The variable editor was not successfully "
|
|
"set up with an object to edit."));
|
|
return false;
|
|
}
|