Files
integration/Plugins/UEWingman/Deprecated/Blueprint_Diff.h

243 lines
7.2 KiB
C
Raw Normal View History

#pragma once
#include "CoreMinimal.h"
2026-03-18 10:17:58 -04:00
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
2026-03-06 20:09:40 -05:00
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
2026-03-12 00:44:17 -04:00
#include "Blueprint_Diff.generated.h"
2026-03-08 22:17:14 -04:00
2026-03-06 20:09:40 -05:00
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
2026-03-15 19:32:09 -04:00
UCLASS()
2026-03-18 10:17:58 -04:00
class UWing_Blueprint_Diff : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
2026-03-14 00:02:00 -04:00
UPROPERTY(meta=(Description="First blueprint package path"))
FString BlueprintA;
2026-03-14 00:02:00 -04:00
UPROPERTY(meta=(Description="Second blueprint package path"))
FString BlueprintB;
UPROPERTY(meta=(Optional, Description="Filter to a specific graph name"))
FString Graph;
virtual FString GetDescription() const override
{
return TEXT("Structural diff between two different Blueprints. Compares nodes, "
"connections, and variables across graphs. Use for comparing variants, "
"finding divergence after copy-paste, or auditing consistency.");
}
virtual void Handle() override
2026-03-06 20:09:40 -05:00
{
// Load both blueprints
2026-03-18 10:17:58 -04:00
WingFetcher FA;
2026-03-14 00:02:00 -04:00
UBlueprint* BPA = FA.Asset(BlueprintA).Cast<UBlueprint>();
if (!BPA) return;
2026-03-06 20:09:40 -05:00
2026-03-18 10:17:58 -04:00
WingFetcher FB;
2026-03-14 00:02:00 -04:00
UBlueprint* BPB = FB.Asset(BlueprintB).Cast<UBlueprint>();
if (!BPB) return;
2026-03-06 20:09:40 -05:00
2026-03-10 07:17:42 -04:00
// Gather graphs, optionally filtering by name
2026-03-06 20:09:40 -05:00
auto GatherGraphs = [this](UBlueprint* BP) -> TArray<UEdGraph*>
{
TArray<UEdGraph*> Graphs;
for (UEdGraph* G : BP->UbergraphPages)
{
if (!G) continue;
2026-03-18 10:17:58 -04:00
if (!Graph.IsEmpty() && !WingUtils::Identifies(Graph, G)) continue;
2026-03-06 20:09:40 -05:00
Graphs.Add(G);
}
for (UEdGraph* G : BP->FunctionGraphs)
{
if (!G) continue;
2026-03-18 10:17:58 -04:00
if (!Graph.IsEmpty() && !WingUtils::Identifies(Graph, G)) continue;
2026-03-06 20:09:40 -05:00
Graphs.Add(G);
}
return Graphs;
};
TArray<UEdGraph*> GraphsA = GatherGraphs(BPA);
TArray<UEdGraph*> GraphsB = GatherGraphs(BPB);
// Build graph name maps
TMap<FString, UEdGraph*> GraphMapA, GraphMapB;
2026-03-18 10:17:58 -04:00
for (UEdGraph* G : GraphsA) GraphMapA.Add(WingUtils::FormatName(G), G);
for (UEdGraph* G : GraphsB) GraphMapB.Add(WingUtils::FormatName(G), G);
2026-03-06 20:09:40 -05:00
// Find all unique graph names
TSet<FString> AllGraphNames;
for (auto& Pair : GraphMapA) AllGraphNames.Add(Pair.Key);
for (auto& Pair : GraphMapB) AllGraphNames.Add(Pair.Key);
2026-03-10 07:17:42 -04:00
int32 TotalDiffs = 0;
2026-03-06 20:09:40 -05:00
for (const FString& GraphName : AllGraphNames)
{
UEdGraph** pGA = GraphMapA.Find(GraphName);
UEdGraph** pGB = GraphMapB.Find(GraphName);
if (!pGA)
{
2026-03-18 10:17:58 -04:00
UWingServer::Printf(TEXT("Graph %s: only in B (%d nodes)\n"), *GraphName, (*pGB)->Nodes.Num());
2026-03-10 07:17:42 -04:00
TotalDiffs++;
2026-03-06 20:09:40 -05:00
continue;
}
if (!pGB)
{
2026-03-18 10:17:58 -04:00
UWingServer::Printf(TEXT("Graph %s: only in A (%d nodes)\n"), *GraphName, (*pGA)->Nodes.Num());
2026-03-10 07:17:42 -04:00
TotalDiffs++;
2026-03-06 20:09:40 -05:00
continue;
}
2026-03-10 07:17:42 -04:00
// Both exist -- compare nodes
2026-03-06 20:09:40 -05:00
UEdGraph* GA = *pGA;
UEdGraph* GB = *pGB;
2026-03-10 07:17:42 -04:00
// Build node title maps for matching
2026-03-06 20:09:40 -05:00
TMap<FString, TArray<UEdGraphNode*>> NodesA, NodesB;
for (UEdGraphNode* N : GA->Nodes)
{
if (!N) continue;
2026-03-18 10:17:58 -04:00
NodesA.FindOrAdd(WingUtils::FormatName(N)).Add(N);
2026-03-06 20:09:40 -05:00
}
for (UEdGraphNode* N : GB->Nodes)
{
if (!N) continue;
2026-03-18 10:17:58 -04:00
NodesB.FindOrAdd(WingUtils::FormatName(N)).Add(N);
2026-03-06 20:09:40 -05:00
}
// Nodes only in A
2026-03-10 07:17:42 -04:00
TArray<FString> OnlyInA;
2026-03-06 20:09:40 -05:00
for (auto& Pair : NodesA)
{
int32 CountA = Pair.Value.Num();
int32 CountB = 0;
2026-03-10 07:17:42 -04:00
if (auto* pArr = NodesB.Find(Pair.Key)) CountB = pArr->Num();
2026-03-06 20:09:40 -05:00
if (CountA > CountB)
2026-03-10 07:17:42 -04:00
OnlyInA.Add(FString::Printf(TEXT(" %s (x%d)"), *Pair.Key, CountA - CountB));
2026-03-06 20:09:40 -05:00
}
// Nodes only in B
2026-03-10 07:17:42 -04:00
TArray<FString> OnlyInB;
2026-03-06 20:09:40 -05:00
for (auto& Pair : NodesB)
{
int32 CountB = Pair.Value.Num();
int32 CountA = 0;
2026-03-10 07:17:42 -04:00
if (auto* pArr = NodesA.Find(Pair.Key)) CountA = pArr->Num();
2026-03-06 20:09:40 -05:00
if (CountB > CountA)
2026-03-10 07:17:42 -04:00
OnlyInB.Add(FString::Printf(TEXT(" %s (x%d)"), *Pair.Key, CountB - CountA));
2026-03-06 20:09:40 -05:00
}
2026-03-10 07:17:42 -04:00
// Connection diff
2026-03-06 20:09:40 -05:00
auto MakeConnKey = [](UEdGraphPin* SrcPin, UEdGraphPin* TgtPin) -> FString
{
2026-03-10 07:17:42 -04:00
return FString::Printf(TEXT("%s|%s|%s|%s"),
2026-03-18 10:17:58 -04:00
*WingUtils::FormatName(SrcPin->GetOwningNode()), *WingUtils::FormatName(SrcPin),
*WingUtils::FormatName(TgtPin->GetOwningNode()), *WingUtils::FormatName(TgtPin));
2026-03-06 20:09:40 -05:00
};
2026-03-10 07:17:42 -04:00
auto GatherConnections = [&MakeConnKey](UEdGraph* G) -> TSet<FString>
2026-03-06 20:09:40 -05:00
{
2026-03-10 07:17:42 -04:00
TSet<FString> Conns;
for (UEdGraphNode* N : G->Nodes)
2026-03-06 20:09:40 -05:00
{
2026-03-10 07:17:42 -04:00
if (!N) continue;
for (UEdGraphPin* Pin : N->Pins)
2026-03-06 20:09:40 -05:00
{
2026-03-10 07:17:42 -04:00
if (!Pin || Pin->Direction != EGPD_Output) continue;
for (UEdGraphPin* Linked : Pin->LinkedTo)
{
if (!Linked || !Linked->GetOwningNode()) continue;
Conns.Add(MakeConnKey(Pin, Linked));
}
2026-03-06 20:09:40 -05:00
}
}
2026-03-10 07:17:42 -04:00
return Conns;
};
2026-03-06 20:09:40 -05:00
2026-03-10 07:17:42 -04:00
TSet<FString> ConnectionsA = GatherConnections(GA);
TSet<FString> ConnectionsB = GatherConnections(GB);
TArray<FString> ConnsOnlyInA, ConnsOnlyInB;
2026-03-06 20:09:40 -05:00
for (const FString& Key : ConnectionsA)
if (!ConnectionsB.Contains(Key))
2026-03-10 07:17:42 -04:00
ConnsOnlyInA.Add(FString::Printf(TEXT(" %s"), *Key));
2026-03-06 20:09:40 -05:00
for (const FString& Key : ConnectionsB)
if (!ConnectionsA.Contains(Key))
2026-03-10 07:17:42 -04:00
ConnsOnlyInB.Add(FString::Printf(TEXT(" %s"), *Key));
2026-03-06 20:09:40 -05:00
2026-03-10 07:17:42 -04:00
bool bIdentical = OnlyInA.IsEmpty() && OnlyInB.IsEmpty() && ConnsOnlyInA.IsEmpty() && ConnsOnlyInB.IsEmpty();
if (bIdentical)
{
2026-03-18 10:17:58 -04:00
UWingServer::Printf(TEXT("Graph %s: identical (%d nodes)\n"), *GraphName, GA->Nodes.Num());
2026-03-10 07:17:42 -04:00
continue;
}
2026-03-06 20:09:40 -05:00
2026-03-10 07:17:42 -04:00
TotalDiffs++;
2026-03-18 10:17:58 -04:00
UWingServer::Printf(TEXT("Graph %s: different (A=%d nodes, B=%d nodes)\n"), *GraphName, GA->Nodes.Num(), GB->Nodes.Num());
2026-03-06 20:09:40 -05:00
2026-03-10 07:17:42 -04:00
if (!OnlyInA.IsEmpty())
{
2026-03-18 10:17:58 -04:00
UWingServer::Print(TEXT(" Nodes only in A:\n"));
for (const FString& Line : OnlyInA) UWingServer::Printf(TEXT(" %s\n"), *Line);
2026-03-10 07:17:42 -04:00
}
if (!OnlyInB.IsEmpty())
{
2026-03-18 10:17:58 -04:00
UWingServer::Print(TEXT(" Nodes only in B:\n"));
for (const FString& Line : OnlyInB) UWingServer::Printf(TEXT(" %s\n"), *Line);
2026-03-10 07:17:42 -04:00
}
if (!ConnsOnlyInA.IsEmpty())
{
2026-03-18 10:17:58 -04:00
UWingServer::Print(TEXT(" Connections only in A:\n"));
for (const FString& Line : ConnsOnlyInA) UWingServer::Printf(TEXT(" %s\n"), *Line);
2026-03-10 07:17:42 -04:00
}
if (!ConnsOnlyInB.IsEmpty())
{
2026-03-18 10:17:58 -04:00
UWingServer::Print(TEXT(" Connections only in B:\n"));
for (const FString& Line : ConnsOnlyInB) UWingServer::Printf(TEXT(" %s\n"), *Line);
2026-03-10 07:17:42 -04:00
}
2026-03-06 20:09:40 -05:00
}
// Compare variables
TSet<FString> VarNamesA, VarNamesB;
2026-03-18 10:17:58 -04:00
for (const FBPVariableDescription& V : BPA->NewVariables) VarNamesA.Add(WingUtils::FormatName(V));
for (const FBPVariableDescription& V : BPB->NewVariables) VarNamesB.Add(WingUtils::FormatName(V));
2026-03-06 20:09:40 -05:00
2026-03-10 07:17:42 -04:00
TArray<FString> VarsOnlyInA, VarsOnlyInB;
2026-03-06 20:09:40 -05:00
for (const FString& Name : VarNamesA)
if (!VarNamesB.Contains(Name))
2026-03-10 07:17:42 -04:00
VarsOnlyInA.Add(Name);
2026-03-06 20:09:40 -05:00
for (const FString& Name : VarNamesB)
if (!VarNamesA.Contains(Name))
2026-03-10 07:17:42 -04:00
VarsOnlyInB.Add(Name);
2026-03-06 20:09:40 -05:00
2026-03-10 07:17:42 -04:00
if (!VarsOnlyInA.IsEmpty())
2026-03-06 20:09:40 -05:00
{
2026-03-18 10:17:58 -04:00
UWingServer::Print(TEXT("Variables only in A:\n"));
for (const FString& Name : VarsOnlyInA) UWingServer::Printf(TEXT(" %s\n"), *Name);
2026-03-10 07:17:42 -04:00
TotalDiffs += VarsOnlyInA.Num();
2026-03-06 20:09:40 -05:00
}
2026-03-10 07:17:42 -04:00
if (!VarsOnlyInB.IsEmpty())
{
2026-03-18 10:17:58 -04:00
UWingServer::Print(TEXT("Variables only in B:\n"));
for (const FString& Name : VarsOnlyInB) UWingServer::Printf(TEXT(" %s\n"), *Name);
2026-03-10 07:17:42 -04:00
TotalDiffs += VarsOnlyInB.Num();
}
2026-03-18 10:17:58 -04:00
UWingServer::Printf(TEXT("Total differences: %d\n"), TotalDiffs);
2026-03-06 20:09:40 -05:00
}
};