LifePlay-Revival/Source/LifePlayEditorTools/LPFixOrphanBoundEventsCommandlet.cpp

201 lines
5.6 KiB
C++
Raw Permalink Normal View History

// Portage UE 5.5 - voir LPFixOrphanBoundEventsCommandlet.h
#include "LPFixOrphanBoundEventsCommandlet.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraphSchema_K2.h"
#include "Engine/Blueprint.h"
#include "K2Node_ComponentBoundEvent.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Misc/PackageName.h"
#include "UObject/Package.h"
#include "UObject/SavePackage.h"
#include "UObject/UObjectGlobals.h"
DEFINE_LOG_CATEGORY_STATIC(LogLPFixBoundEvents, Log, All);
namespace
{
/**
* Reproduit le test que le compilateur applique lui-meme : le composant referencE par
* l'evenement doit exister sous forme de FObjectProperty sur la classe squelette du
* Blueprint. UK2Node_ComponentBoundEvent::IsDelegateValid() est prive, d'ou la copie.
*/
bool ComponentStillExists(const UBlueprint* Blueprint, FName ComponentPropertyName)
{
if (!Blueprint || ComponentPropertyName.IsNone())
{
return false;
}
const UClass* Scope = Blueprint->SkeletonGeneratedClass
? Blueprint->SkeletonGeneratedClass
: Blueprint->GeneratedClass;
return Scope && FindFProperty<FObjectProperty>(Scope, ComponentPropertyName) != nullptr;
}
/** Compte les noeuds atteignables depuis la broche Exec de sortie de l'evenement. */
int32 CountDownstreamNodes(const UEdGraphNode* EventNode)
{
TSet<const UEdGraphNode*> Seen;
TArray<const UEdGraphNode*> Pending;
Pending.Add(EventNode);
Seen.Add(EventNode);
while (Pending.Num() > 0)
{
const UEdGraphNode* Current = Pending.Pop();
for (const UEdGraphPin* Pin : Current->Pins)
{
if (!Pin || Pin->Direction != EGPD_Output || Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)
{
continue;
}
for (const UEdGraphPin* Linked : Pin->LinkedTo)
{
const UEdGraphNode* Next = Linked ? Linked->GetOwningNode() : nullptr;
if (Next && !Seen.Contains(Next))
{
Seen.Add(Next);
Pending.Add(Next);
}
}
}
}
return Seen.Num() - 1; // on ne compte pas l'evenement lui-meme
}
}
ULPFixOrphanBoundEventsCommandlet::ULPFixOrphanBoundEventsCommandlet()
{
IsClient = false;
IsServer = false;
IsEditor = true;
LogToConsole = true;
}
int32 ULPFixOrphanBoundEventsCommandlet::Main(const FString& Params)
{
const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase);
UE_LOG(LogLPFixBoundEvents, Warning, TEXT("=== Evenements de composant orphelins (R-05)%s ==="),
bDryRun ? TEXT(" [DRY RUN]") : TEXT(""));
FAssetRegistryModule& AssetRegistryModule =
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
AssetRegistryModule.Get().SearchAllAssets(true);
TArray<FAssetData> BlueprintAssets;
AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true);
int32 NodesFound = 0;
int32 NodesRemoved = 0;
int32 BlueprintsModified = 0;
int32 SaveFailures = 0;
for (const FAssetData& AssetData : BlueprintAssets)
{
if (!AssetData.PackageName.ToString().StartsWith(TEXT("/Game/")))
{
continue;
}
UBlueprint* Blueprint = Cast<UBlueprint>(AssetData.GetAsset());
if (!Blueprint)
{
continue;
}
TArray<UEdGraph*> Graphs;
Blueprint->GetAllGraphs(Graphs);
int32 RemovedHere = 0;
for (UEdGraph* Graph : Graphs)
{
if (!Graph)
{
continue;
}
TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
for (UEdGraphNode* GraphNode : Snapshot)
{
UK2Node_ComponentBoundEvent* BoundEvent = Cast<UK2Node_ComponentBoundEvent>(GraphNode);
if (!BoundEvent)
{
continue;
}
const FName ComponentName = BoundEvent->GetComponentPropertyName();
if (ComponentStillExists(Blueprint, ComponentName))
{
continue;
}
++NodesFound;
const int32 Downstream = CountDownstreamNodes(BoundEvent);
UE_LOG(LogLPFixBoundEvents, Warning,
TEXT(" %s :: %s -> %s (composant '%s' introuvable, %d noeud(s) en aval)"),
*Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(),
*ComponentName.ToString(), Downstream);
if (bDryRun)
{
continue;
}
Graph->Modify();
BoundEvent->Modify();
BoundEvent->DestroyNode();
++NodesRemoved;
++RemovedHere;
}
}
if (RemovedHere > 0 && !bDryRun)
{
++BlueprintsModified;
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
FKismetEditorUtilities::CompileBlueprint(Blueprint);
UPackage* Package = Blueprint->GetOutermost();
FString Filename;
if (!FPackageName::TryConvertLongPackageNameToFilename(
Package->GetName(), Filename, FPackageName::GetAssetPackageExtension()))
{
++SaveFailures;
UE_LOG(LogLPFixBoundEvents, Error, TEXT(" chemin introuvable pour %s"), *Package->GetName());
continue;
}
ResetLoaders(Package);
FSavePackageArgs SaveArgs;
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
SaveArgs.SaveFlags = SAVE_None;
SaveArgs.bWarnOfLongFilename = true;
SaveArgs.Error = GWarn;
if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs))
{
UE_LOG(LogLPFixBoundEvents, Warning, TEXT(" -> %s sauvegarde (%d evenement(s) retire(s))."),
*Package->GetName(), RemovedHere);
}
else
{
++SaveFailures;
UE_LOG(LogLPFixBoundEvents, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName());
}
}
}
UE_LOG(LogLPFixBoundEvents, Warning,
TEXT("=== Termine : %d evenement(s) orphelin(s) trouve(s), %d retire(s) dans %d Blueprint(s), %d echec(s) de sauvegarde. ==="),
NodesFound, NodesRemoved, BlueprintsModified, SaveFailures);
return SaveFailures > 0 ? 1 : 0;
}