LifePlay-Revival/Source/LifePlayEditorTools/LPFixAssetRegistryPinsCommandlet.cpp

320 lines
9.8 KiB
C++
Raw Normal View History

// Portage UE 5.5 - voir LPFixAssetRegistryPinsCommandlet.h
#include "LPFixAssetRegistryPinsCommandlet.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraphSchema_K2.h"
#include "Engine/Blueprint.h"
#include "K2Node_BreakStruct.h"
#include "K2Node_CallFunction.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "LPAssetRegistryHelpers.h"
#include "Misc/PackageName.h"
#include "UObject/Package.h"
#include "UObject/SavePackage.h"
#include "UObject/UObjectGlobals.h"
DEFINE_LOG_CATEGORY_STATIC(LogLPFixARPins, Log, All);
namespace
{
const FName DeprecatedClassNamePin(TEXT("ClassName"));
const FName DeprecatedObjectPathPin(TEXT("ObjectPath"));
const FName AssetDataPin(TEXT("AssetData"));
const FName AssetDataStructName(TEXT("AssetData"));
const FName GetAssetsByClassFunction(TEXT("GetAssetsByClass"));
const FName ReplacementEnumFunction(TEXT("GetAssetsByClassName"));
const FName ReplacementPathFunction(TEXT("GetAssetObjectPath"));
/** Broche orpheline portant ce nom, s'il y en a une. */
UEdGraphPin* FindOrphan(UEdGraphNode* Node, FName PinName)
{
for (UEdGraphPin* Pin : Node->Pins)
{
if (Pin && Pin->bOrphanedPin && Pin->PinName == PinName)
{
return Pin;
}
}
return nullptr;
}
}
ULPFixAssetRegistryPinsCommandlet::ULPFixAssetRegistryPinsCommandlet()
{
IsClient = false;
IsServer = false;
IsEditor = true;
LogToConsole = true;
}
int32 ULPFixAssetRegistryPinsCommandlet::Main(const FString& Params)
{
const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase);
UFunction* EnumFunction = ULPAssetRegistryHelpers::StaticClass()->FindFunctionByName(ReplacementEnumFunction);
UFunction* PathFunction = ULPAssetRegistryHelpers::StaticClass()->FindFunctionByName(ReplacementPathFunction);
if (!EnumFunction || !PathFunction)
{
UE_LOG(LogLPFixARPins, Error, TEXT("Fonctions de remplacement introuvables (%s=%s, %s=%s)."),
*ReplacementEnumFunction.ToString(), EnumFunction ? TEXT("ok") : TEXT("NULL"),
*ReplacementPathFunction.ToString(), PathFunction ? TEXT("ok") : TEXT("NULL"));
return 1;
}
UE_LOG(LogLPFixARPins, Warning, TEXT("=== Broches AssetRegistry perdues a la migration 5.1%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 EnumFixed = 0;
int32 PathFixed = 0;
int32 Unhandled = 0;
int32 BlueprintsModified = 0;
int32 SaveFailures = 0;
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
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 FixedHere = 0;
for (UEdGraph* Graph : Graphs)
{
if (!Graph)
{
continue;
}
TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
for (UEdGraphNode* GraphNode : Snapshot)
{
// -------------------------------------------------------------------
// 1. GetAssetsByClass : la valeur de classe est restee sur l'orpheline.
// On bascule sur l'enrobage FName, qui la reprend telle quelle.
// -------------------------------------------------------------------
if (UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(GraphNode))
{
if (CallNode->FunctionReference.GetMemberName() != GetAssetsByClassFunction)
{
continue;
}
UEdGraphPin* Orphan = FindOrphan(CallNode, DeprecatedClassNamePin);
if (!Orphan)
{
continue; // deja corrige, ou noeud sain
}
const FString ClassValue = Orphan->DefaultValue;
UE_LOG(LogLPFixARPins, Warning, TEXT(" [enum] %s :: %s -> %s | classe='%s'"),
*Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(), *ClassValue);
if (ClassValue.IsEmpty())
{
++Unhandled;
UE_LOG(LogLPFixARPins, Error, TEXT(" orpheline vide : rien a reporter, reprise manuelle."));
continue;
}
if (bDryRun)
{
continue;
}
CallNode->Modify();
// La broche orpheline doit disparaitre AVANT la reconstruction, sinon
// ReconstructNode la conserve (garde-fou d'UE contre la perte de donnees).
Orphan->BreakAllPinLinks();
Orphan->DefaultValue.Empty();
CallNode->SetFromFunction(EnumFunction);
CallNode->ReconstructNode();
// La fonction de remplacement a une broche ClassName de meme nom : on y
// remet la valeur recuperee.
if (UEdGraphPin* NewPin = CallNode->FindPin(DeprecatedClassNamePin, EGPD_Input))
{
NewPin->DefaultValue = ClassValue;
UE_LOG(LogLPFixARPins, Warning, TEXT(" -> GetAssetsByClassName(ClassName='%s')"), *ClassValue);
++EnumFixed;
++FixedHere;
}
else
{
++Unhandled;
UE_LOG(LogLPFixARPins, Error, TEXT(" broche ClassName absente apres reconstruction."));
}
continue;
}
// -------------------------------------------------------------------
// 2. Break AssetData : la sortie ObjectPath n'existe plus. On insere un
// appel a GetAssetObjectPath alimente par la meme structure, et on lui
// transfere les liaisons restees sur l'orpheline.
// -------------------------------------------------------------------
UK2Node_BreakStruct* BreakNode = Cast<UK2Node_BreakStruct>(GraphNode);
if (!BreakNode || !BreakNode->StructType ||
BreakNode->StructType->GetFName() != AssetDataStructName)
{
continue;
}
UEdGraphPin* Orphan = FindOrphan(BreakNode, DeprecatedObjectPathPin);
if (!Orphan)
{
continue;
}
UEdGraphPin* StructInput = BreakNode->FindPin(AssetDataPin, EGPD_Input);
UE_LOG(LogLPFixARPins, Warning, TEXT(" [path] %s :: %s -> %s | %d consommateur(s)"),
*Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(), Orphan->LinkedTo.Num());
if (Orphan->LinkedTo.Num() == 0)
{
// Personne ne la lit : la vider suffit a la faire disparaitre.
if (!bDryRun)
{
BreakNode->Modify();
Orphan->DefaultValue.Empty();
BreakNode->ReconstructNode();
++PathFixed;
++FixedHere;
}
continue;
}
if (!StructInput || StructInput->LinkedTo.Num() == 0)
{
++Unhandled;
UE_LOG(LogLPFixARPins, Error, TEXT(" entree AssetData non connectee : reprise manuelle."));
continue;
}
if (bDryRun)
{
continue;
}
// Noeud de remplacement, place juste sous le Break.
UK2Node_CallFunction* PathNode = NewObject<UK2Node_CallFunction>(Graph);
PathNode->SetFromFunction(PathFunction);
PathNode->NodePosX = BreakNode->NodePosX;
PathNode->NodePosY = BreakNode->NodePosY + 120;
PathNode->CreateNewGuid();
PathNode->PostPlacedNewNode();
PathNode->AllocateDefaultPins();
Graph->Modify();
Graph->AddNode(PathNode, false, false);
UEdGraphPin* PathStructIn = PathNode->FindPin(AssetDataPin, EGPD_Input);
UEdGraphPin* PathReturn = PathNode->GetReturnValuePin();
bool bOk = PathStructIn && PathReturn;
// Meme source de structure que le Break.
if (bOk)
{
UEdGraphPin* Source = StructInput->LinkedTo[0];
bOk = Schema->TryCreateConnection(Source, PathStructIn);
if (!bOk)
{
UE_LOG(LogLPFixARPins, Error, TEXT(" source AssetData incompatible avec la nouvelle entree."));
}
}
// Transfert des consommateurs de l'orpheline vers la nouvelle sortie.
if (bOk)
{
const FPinConnectionResponse R = Schema->MovePinLinks(*Orphan, *PathReturn);
bOk = R.Response != CONNECT_RESPONSE_DISALLOW;
if (!bOk)
{
UE_LOG(LogLPFixARPins, Error, TEXT(" transfert impossible (%s)."), *R.Message.ToString());
}
}
if (!bOk)
{
++Unhandled;
PathNode->DestroyNode();
continue;
}
BreakNode->Modify();
Orphan->BreakAllPinLinks();
Orphan->DefaultValue.Empty();
BreakNode->ReconstructNode();
UE_LOG(LogLPFixARPins, Warning, TEXT(" -> GetAssetObjectPath inserE"));
++PathFixed;
++FixedHere;
}
}
if (FixedHere > 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(LogLPFixARPins, 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;
// GWarn et non GError : en commandlet, GError rend toute erreur de save fatale.
SaveArgs.Error = GWarn;
if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs))
{
UE_LOG(LogLPFixARPins, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere);
}
else
{
++SaveFailures;
UE_LOG(LogLPFixARPins, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName());
}
}
}
UE_LOG(LogLPFixARPins, Warning,
TEXT("=== Termine : %d enumeration(s), %d chemin(s), %d Blueprint(s) modifie(s), %d cas non traite(s), %d echec(s). ==="),
EnumFixed, PathFixed, BlueprintsModified, Unhandled, SaveFailures);
return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0;
}