Lots more progress on the name overhaul
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include "WingHandler.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingUtils.h"
|
||||
#include "WingTypes.h"
|
||||
#include "WingServer.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Kismet2/BlueprintEditorUtils.h"
|
||||
@@ -41,11 +42,26 @@ public:
|
||||
UBlueprint* BP = F.Asset(Blueprint).Cast<UBlueprint>();
|
||||
if (!BP) return;
|
||||
|
||||
// Find the interface by name
|
||||
FBPInterfaceDescription *IFaceDesc =
|
||||
WingUtils::FindExactlyOneNamed(Interface, BP->ImplementedInterfaces, TEXT("Interface"));
|
||||
if (!IFaceDesc) return;
|
||||
UClass* FoundInterface = IFaceDesc->Interface;
|
||||
// Resolve the interface name to a UClass*
|
||||
UWingTypes::Requirements Req;
|
||||
Req.BlueprintType = false;
|
||||
Req.Blueprintable = false;
|
||||
Req.AllowContainer = false;
|
||||
UClass* FoundInterface = UWingTypes::TextToOneInterfaceType(Interface, Req);
|
||||
if (!FoundInterface) return;
|
||||
|
||||
// Verify this blueprint actually implements it
|
||||
bool Found = false;
|
||||
for (const FBPInterfaceDescription& Desc : BP->ImplementedInterfaces)
|
||||
{
|
||||
if (Desc.Interface == FoundInterface) { Found = true; break; }
|
||||
}
|
||||
if (!Found)
|
||||
{
|
||||
UWingServer::Printf(TEXT("ERROR: Blueprint %s does not implement interface %s\n"),
|
||||
*WingUtils::FormatName(BP), *WingUtils::FormatName(FoundInterface));
|
||||
return;
|
||||
}
|
||||
|
||||
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
|
||||
FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions);
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingUtils.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "GraphNode_Duplicate.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UWing_GraphNode_Duplicate : public UObject, public IWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Description="Target graph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(meta=(Description="Array of node names to duplicate (as returned by FormatName)"))
|
||||
FWingJsonArray Nodes;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="X offset for duplicated nodes"))
|
||||
int32 OffsetX = 50;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Y offset for duplicated nodes"))
|
||||
int32 OffsetY = 50;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
return TEXT("Duplicate one or more nodes in a Blueprint graph. "
|
||||
"Creates copies offset from the originals with new GUIDs. "
|
||||
"Connections are not preserved on the duplicates.");
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingFetcher F;
|
||||
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
|
||||
if (!TargetGraph) return;
|
||||
|
||||
if (Nodes.Array.Num() == 0)
|
||||
{
|
||||
UWingServer::Print(TEXT("ERROR: Nodes array is empty\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all source nodes by name
|
||||
TArray<UEdGraphNode*> SourceNodes;
|
||||
for (const TSharedPtr<FJsonValue>& IdVal : Nodes.Array)
|
||||
{
|
||||
FString Name = IdVal->AsString();
|
||||
UEdGraphNode* Found = nullptr;
|
||||
for (UEdGraphNode* Node : TargetGraph->Nodes)
|
||||
{
|
||||
if (WingUtils::Identifies(Name, Node))
|
||||
{
|
||||
Found = Node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!Found)
|
||||
{
|
||||
UWingServer::Printf(TEXT("ERROR: Node '%s' not found in graph\n"), *Name);
|
||||
continue;
|
||||
}
|
||||
SourceNodes.Add(Found);
|
||||
}
|
||||
|
||||
if (SourceNodes.Num() == 0) return;
|
||||
|
||||
// Duplicate each node
|
||||
for (UEdGraphNode* SourceNode : SourceNodes)
|
||||
{
|
||||
UEdGraphNode* NewNode = DuplicateObject<UEdGraphNode>(SourceNode, TargetGraph);
|
||||
if (!NewNode)
|
||||
{
|
||||
UWingServer::Printf(TEXT("ERROR: Failed to duplicate %s\n"), *WingUtils::FormatName(SourceNode));
|
||||
continue;
|
||||
}
|
||||
|
||||
NewNode->CreateNewGuid();
|
||||
NewNode->NodePosX += OffsetX;
|
||||
NewNode->NodePosY += OffsetY;
|
||||
|
||||
for (UEdGraphPin* Pin : NewNode->Pins)
|
||||
{
|
||||
if (Pin)
|
||||
Pin->LinkedTo.Empty();
|
||||
}
|
||||
|
||||
TargetGraph->AddNode(NewNode, false, false);
|
||||
UWingServer::Printf(TEXT("Duplicated: %s -> %s\n"), *WingUtils::FormatName(SourceNode), *WingUtils::FormatName(NewNode));
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
@@ -92,12 +92,12 @@ public:
|
||||
if (!Node) return;
|
||||
|
||||
TArray<FWingProperty> All = FWingProperty::GetDetailsMutable(Node, CPF_Edit);
|
||||
FWingProperty P = FWingProperty::FindOneExactMatch(All, Entry.Name);
|
||||
FWingProperty *P = WingUtils::FindOneWithExternalID(Entry.Name, All, TEXT("Property"));
|
||||
if (!P) return;
|
||||
|
||||
UWingServer::AddTouchedObject(Node);
|
||||
|
||||
if (!P.SetText(Entry.Value))
|
||||
if (!P->SetText(Entry.Value))
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,10 @@ public:
|
||||
if (!Obj) return;
|
||||
|
||||
TArray<FWingProperty> All = FWingProperty::GetDetailsMutable(Obj, CPF_Edit);
|
||||
FWingProperty P = FWingProperty::FindOneExactMatch(All, Property);
|
||||
FWingProperty *P = WingUtils::FindOneWithExternalID(Property, All, TEXT("Property"));
|
||||
if (!P) return;
|
||||
|
||||
UWingServer::Print(P.GetText());
|
||||
UWingServer::Print(P->GetText());
|
||||
UWingServer::Print(TEXT("\n"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,15 +48,15 @@ public:
|
||||
// Validation pass — resolve all properties and values before modifying anything.
|
||||
for (const auto& Pair : Properties.Json->Values)
|
||||
{
|
||||
FWingProperty P = FWingProperty::FindOneExactMatch(All, Pair.Key);
|
||||
FWingProperty *P = WingUtils::FindOneWithExternalID(Pair.Key, All, TEXT("Property"));
|
||||
if (!P) return;
|
||||
}
|
||||
|
||||
// Assignment Pass - store the values.
|
||||
for (const auto& Pair : Properties.Json->Values)
|
||||
{
|
||||
FWingProperty P = FWingProperty::FindOneExactMatch(All, Pair.Key);
|
||||
if (P.SetJson(Pair.Value)) SuccessCount++;
|
||||
FWingProperty *P = WingUtils::FindOneWithExternalID(Pair.Key, All, TEXT("Property"));
|
||||
if (P && P->SetJson(Pair.Value)) SuccessCount++;
|
||||
}
|
||||
|
||||
UWingServer::Printf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num());
|
||||
|
||||
@@ -336,27 +336,6 @@ TArray<FWingProperty> FWingProperty::FindAllSubstring(const TArray<FWingProperty
|
||||
return Result;
|
||||
}
|
||||
|
||||
FWingProperty FWingProperty::FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name)
|
||||
{
|
||||
TArray<FWingProperty> Matches;
|
||||
for (const FWingProperty& P : Props)
|
||||
{
|
||||
if (WingUtils::Identifies(Name, P.Prop))
|
||||
Matches.Add(P);
|
||||
}
|
||||
if (Matches.Num() == 0)
|
||||
{
|
||||
UWingServer::Printf(TEXT("ERROR: Property '%s' not found\n"), *Name);
|
||||
return FWingProperty();
|
||||
}
|
||||
if (Matches.Num() > 1)
|
||||
{
|
||||
UWingServer::Printf(TEXT("ERROR: Ambiguous property '%s'\n"), *Name);
|
||||
return FWingProperty();
|
||||
}
|
||||
return Matches[0];
|
||||
}
|
||||
|
||||
bool FWingProperty::PopulateFromJson(FWingProperty& P, const FJsonObject* Json, bool AllOptional)
|
||||
{
|
||||
FString JsonKey = WingUtils::FormatName(P.Prop);
|
||||
|
||||
@@ -311,3 +311,14 @@ FString WingTokenizer::CheckInternalizeID(const FString &ExternalID)
|
||||
}
|
||||
return InternalID;
|
||||
}
|
||||
|
||||
FString WingTokenizer::SimplifyID(const FString &ID)
|
||||
{
|
||||
TStringBuilder<512> Result;
|
||||
for (TCHAR Ch : ID)
|
||||
{
|
||||
if (WingCharacterClasses::GetCat(Ch) == Cat::Identifier)
|
||||
Result.AppendChar(Ch);
|
||||
}
|
||||
return Result.ToString();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "WingUtils.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingManual.h"
|
||||
#include "WingTokenizer.h"
|
||||
#include "Editor.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
@@ -161,15 +162,17 @@ FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, const U
|
||||
// Verify that the path is not already associated.
|
||||
check(!PathToShort.Find(Path));
|
||||
|
||||
// Derive the name proposal from the path.
|
||||
// Derive the name proposal from the path. Names used by
|
||||
// the type system are always simple ids that require no
|
||||
// escaping when emitted.
|
||||
int32 DotIndex;
|
||||
check(Path.FindLastChar('.', DotIndex));
|
||||
FString Proposal = Path.Mid(DotIndex + 1);
|
||||
if ((PinCategory == UEdGraphSchema_K2::PC_Object || PinCategory == UEdGraphSchema_K2::PC_Interface)
|
||||
&& IsUserDefined && Proposal.EndsWith(TEXT("_C")))
|
||||
Proposal.LeftChopInline(2);
|
||||
Proposal = WingUtils::ExternalizeID(Proposal);
|
||||
check(!Proposal.IsEmpty());
|
||||
Proposal = WingTokenizer::SimplifyID(Proposal);
|
||||
if (Proposal.IsEmpty()) Proposal = "Unknown";
|
||||
|
||||
// Construct the Info struct.
|
||||
Info TypeInfo;
|
||||
|
||||
@@ -55,10 +55,7 @@
|
||||
// Name sanitization
|
||||
// ============================================================
|
||||
|
||||
FString WingUtils::ExternalizeID(const FString &InName)
|
||||
{
|
||||
return WingTokenizer::ExternalizeID(InName);
|
||||
}
|
||||
FName WingUtils::GetFName(const FWingProperty &Prop) { return Prop.Prop->GetFName(); }
|
||||
|
||||
FString WingUtils::ExternalizeID(FName Name)
|
||||
{
|
||||
@@ -126,7 +123,7 @@ FString WingUtils::FormatName(const UBlueprint *BP)
|
||||
|
||||
FString WingUtils::FormatName(const UActorComponent *C)
|
||||
{
|
||||
return ExternalizeID(C->GetName());
|
||||
return ExternalizeID(C->GetFName());
|
||||
}
|
||||
|
||||
FString WingUtils::FormatName(const USCS_Node *Node)
|
||||
@@ -136,22 +133,22 @@ FString WingUtils::FormatName(const USCS_Node *Node)
|
||||
|
||||
FString WingUtils::FormatName(const UEdGraph *Graph)
|
||||
{
|
||||
return ExternalizeID(Graph->GetName());
|
||||
return ExternalizeID(Graph->GetFName());
|
||||
}
|
||||
|
||||
FString WingUtils::FormatName(const TObjectPtr<UEdGraph> &Graph)
|
||||
{
|
||||
return ExternalizeID(Graph->GetName());
|
||||
return ExternalizeID(Graph->GetFName());
|
||||
}
|
||||
|
||||
FString WingUtils::FormatName(const UEdGraphNode* Node)
|
||||
{
|
||||
return ExternalizeID(Node->GetName());
|
||||
return ExternalizeID(Node->GetFName());
|
||||
}
|
||||
|
||||
FString WingUtils::FormatName(const UEdGraphPin *Pin)
|
||||
{
|
||||
return ExternalizeID(Pin->GetName());
|
||||
return ExternalizeID(Pin->GetFName());
|
||||
}
|
||||
|
||||
FString WingUtils::FormatName(const FMemberReference &Ref)
|
||||
@@ -168,7 +165,7 @@ FString WingUtils::FormatName(const UStruct *Struct)
|
||||
{
|
||||
if (Cast<UScriptStruct>(Struct) || Cast<UClass>(Struct))
|
||||
return UWingTypes::TypeToTextOrDie(Struct);
|
||||
return ExternalizeID(Struct->GetName());
|
||||
return ExternalizeID(Struct->GetFName());
|
||||
}
|
||||
|
||||
FString WingUtils::FormatName(const UClass *Class)
|
||||
@@ -193,7 +190,7 @@ FString WingUtils::FormatName(const UMaterialFunction *MaterialFunction)
|
||||
|
||||
FString WingUtils::FormatName(const UMaterialExpression *Expression)
|
||||
{
|
||||
return ExternalizeID(Expression->GetName());
|
||||
return ExternalizeID(Expression->GetFName());
|
||||
}
|
||||
|
||||
FString WingUtils::FormatName(const UStaticMesh *Mesh)
|
||||
@@ -233,7 +230,7 @@ FString WingUtils::FormatName(const UEnum *Enum)
|
||||
|
||||
FString WingUtils::FormatName(const FProperty *Prop)
|
||||
{
|
||||
return ExternalizeID(Prop->GetName());
|
||||
return ExternalizeID(Prop->GetFName());
|
||||
}
|
||||
|
||||
FString WingUtils::FormatName(const FUserPinInfo &Pin)
|
||||
@@ -253,7 +250,7 @@ FString WingUtils::FormatName(const UWingComponentReference *Ref)
|
||||
|
||||
FString WingUtils::FormatName(const UWidget *Widget)
|
||||
{
|
||||
return ExternalizeID(Widget->GetName());
|
||||
return ExternalizeID(Widget->GetFName());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -62,7 +62,6 @@ struct FWingProperty
|
||||
// Functions to find items by name in an array of properties.
|
||||
//
|
||||
static TArray<FWingProperty> FindAllSubstring(const TArray<FWingProperty>& Props, const FString& Substring);
|
||||
static FWingProperty FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name);
|
||||
static void Remove(TArray<FWingProperty>& Props, const FString& Name);
|
||||
static void Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In, const FString &Name);
|
||||
|
||||
|
||||
@@ -150,6 +150,13 @@ struct WingTokenizer
|
||||
// string.
|
||||
static FString CheckInternalizeID(const FString &ExternalID);
|
||||
|
||||
// Simplify an ID. This removes any non-identifier
|
||||
// characters from the ID. Be careful! This could
|
||||
// remove the whole identifier! So obviously this
|
||||
// should only be used in certain rare contexts where
|
||||
// that's OK.
|
||||
static FString SimplifyID(const FString &ID);
|
||||
|
||||
// Print all tokens to the log for debugging.
|
||||
void PrintEverything() const;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ class UAnimStateTransitionNode;
|
||||
class UScriptStruct;
|
||||
class UEnum;
|
||||
struct FBPInterfaceDescription;
|
||||
struct FWingProperty;
|
||||
|
||||
#include "Engine/World.h"
|
||||
#include "Materials/Material.h"
|
||||
@@ -37,21 +38,14 @@ struct FBPInterfaceDescription;
|
||||
class WingUtils
|
||||
{
|
||||
public:
|
||||
// Given an array and a reference to an element of the array, return
|
||||
// a pointer to the element, or if the element is already a pointer, the element.
|
||||
template<class T> static const T* EltAsPtr(const TArray<T> &A, const T &Elt) { return &Elt; }
|
||||
template<class T> static T* EltAsPtr(TArray<T> &A, T &Elt) { return &Elt; }
|
||||
template<class T> static T* EltAsPtr(const TArray<T*> &A, T* &Elt) { return Elt; }
|
||||
template<class T> static T* EltAsPtr(TArray<T*> &A, T* &Elt) { return Elt; }
|
||||
template<class T> static const T* EltAsPtr(const TArray<const T*> &A, const T* &Elt) { return Elt; }
|
||||
template<class T> static const T* EltAsPtr(TArray<const T*> &A, const T* &Elt) { return Elt; }
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// GetFName
|
||||
//
|
||||
// For objects whose FormatName uses ExternalizeID,
|
||||
// GetFName returns the raw internal FName.
|
||||
// Get the FName of anything that has an FName. This
|
||||
// is here because having a consistent way to get the FName
|
||||
// of something makes it possible to write search templates
|
||||
// that work on any of these types.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
@@ -64,10 +58,45 @@ public:
|
||||
static FName GetFName(const FBPVariableDescription &Var) { return Var.VarName; }
|
||||
static FName GetFName(const UMaterialExpression *Expression) { return Expression->GetFName(); }
|
||||
static FName GetFName(const FProperty *Prop) { return Prop->GetFName(); }
|
||||
static FName GetFName(const FWingProperty &Prop);
|
||||
static FName GetFName(const FUserPinInfo &Pin) { return Pin.PinName; }
|
||||
static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; }
|
||||
static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); }
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// When searching an array of structs, you want to
|
||||
// return a pointer to one of the structs. When
|
||||
// searching an array of pointers, you want to return
|
||||
// one of the pointers. These helpers make it easy for
|
||||
// the search templates below to do that.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
template<class T> static const T* EltAsPtr(const TArray<T> &A, const T &Elt) { return &Elt; }
|
||||
template<class T> static T* EltAsPtr(TArray<T> &A, T &Elt) { return &Elt; }
|
||||
template<class T> static T* EltAsPtr(const TArray<T*> &A, T* &Elt) { return Elt; }
|
||||
template<class T> static T* EltAsPtr(TArray<T*> &A, T* &Elt) { return Elt; }
|
||||
template<class T> static const T* EltAsPtr(const TArray<const T*> &A, const T* &Elt) { return Elt; }
|
||||
template<class T> static const T* EltAsPtr(TArray<const T*> &A, const T* &Elt) { return Elt; }
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Search Templates. Search an array for an item with a
|
||||
// given name. Works correctly no matter how a name is
|
||||
// expressed: eg, "A+B" finds the same results as
|
||||
// "A+B". Handles failure cases correctly: two
|
||||
// items with the same name, no such item, and malformed
|
||||
// names. In case of failure, sends readable and
|
||||
// informative error messages to the AI Agent.
|
||||
//
|
||||
// Try to direct all searching through these templates,
|
||||
// given the amount of care that was put into ensuring
|
||||
// their correctness and the quality of their error
|
||||
// reporting.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
template<typename ArrayType>
|
||||
static auto FindOneWithInternalID(FName InternalID, ArrayType &&Array, const TCHAR *Kind)
|
||||
{
|
||||
@@ -103,32 +132,13 @@ public:
|
||||
return FindNoneWithInternalID(InternalID, Array, Kind);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// GetPathName
|
||||
//
|
||||
// For objects whose FormatName returns a path name,
|
||||
// GetPathName returns the raw path.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
static FString GetPathName(const UWorld *World) { return World->GetPathName(); }
|
||||
static FString GetPathName(const UMaterial *Material) { return Material->GetPathName(); }
|
||||
static FString GetPathName(const UMaterialInstance *MI) { return MI->GetPathName(); }
|
||||
static FString GetPathName(const UMaterialFunction *MF) { return MF->GetPathName(); }
|
||||
static FString GetPathName(const UStaticMesh *Mesh) { return Mesh->GetPathName(); }
|
||||
static FString GetPathName(const USkeletalMesh *Mesh) { return Mesh->GetPathName(); }
|
||||
static FString GetPathName(const UAnimSequence *Anim) { return Anim->GetPathName(); }
|
||||
static FString GetPathName(const UTexture *Texture) { return Texture->GetPathName(); }
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name Formatting
|
||||
//
|
||||
// The goal here is to centralize the code that outputs
|
||||
// names, and have everybody use it, so that names are
|
||||
// used consistently. When an identifier is output by
|
||||
// FormatName, it's always an ExternalID.
|
||||
// Get the name of something, and format it for transmission
|
||||
// to the AI Agent. These correctly handle all escaping and
|
||||
// quoting.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
@@ -161,54 +171,9 @@ public:
|
||||
static FString FormatName(const UWingComponentReference *Ref);
|
||||
static FString FormatName(const UWidget *Widget);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// Identifies
|
||||
//
|
||||
// Return true if the name matches the formatted name
|
||||
// of the object, using the formatname routines above.
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
template<typename T>
|
||||
static bool Identifies(const FString &ExternalID, T&& Obj)
|
||||
{
|
||||
return FormatName(std::forward<T>(Obj)).Equals(ExternalID, ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
template<typename T>
|
||||
static T* FindExactlyOneNamed(const FString &Name, const TArray<T*> &Array, const TCHAR *Kind)
|
||||
{
|
||||
int Count = 0;
|
||||
T* Result = nullptr;
|
||||
for (T* Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = Elt; }
|
||||
if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr;
|
||||
return Result;
|
||||
}
|
||||
|
||||
template<typename T, typename = std::enable_if_t<!std::is_pointer_v<T>>>
|
||||
static T* FindExactlyOneNamed(const FString &Name, TArray<T> &Array, const TCHAR *Kind)
|
||||
{
|
||||
int Count = 0;
|
||||
T* Result = nullptr;
|
||||
for (T& Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = &Elt; }
|
||||
if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr;
|
||||
return Result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static bool FindExactlyNoneNamed(const FString &Name, const TArray<T> &Array, const TCHAR *Kind)
|
||||
{
|
||||
for (const T& Elt: Array) if (Identifies(Name, Elt))
|
||||
{
|
||||
return CheckExactlyNoneNamed(1, Kind, Name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
static FString ExternalizeID(const FString& InternalID);
|
||||
// static FString ExternalizeID(const FString& InternalID);
|
||||
static FString ExternalizeID(FName Name);
|
||||
|
||||
static FName CheckInternalizeID(const FString &ExternalID);
|
||||
|
||||
Reference in New Issue
Block a user