Etape 4b : WhatWhereToArray en C++ (LPDataCompat), 2 appels de GI rebranches par LPFixWhatWhere, verification integree
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
03e5baafdd
commit
5e88dacb4b
6 changed files with 557 additions and 0 deletions
Binary file not shown.
|
|
@ -922,3 +922,4 @@ Validation L7c/L7d (2026-10-02, parc, même cadrage) : la nuit reste claire (sol
|
|||
| CHG-075 | 2026-10-02 | DATA1 | `Plugins/ModdingExtra/.../ModdingExtraBPLibrary.cpp` : statistiques de `LoadTxt` par type de fichier (bilan à la fermeture, commande `ModdingExtra.LoadTxtStats`) ; `11_Audit_Optimisation.md` §17 (stratégie de couche de données) | étape 1 de la couche de données : mesurer avant de construire ; `PopulateRaws` = 12,6 s par chargement de partie (trace O2) ; build `LifePlay_Build_5.5_DATA1` | `git checkout DATA1-avant -- Plugins/ModdingExtra` |
|
||||
| CHG-076 | 2026-10-02 | DATA23 | nouveaux `Plugins/ModdingExtra/.../LPDataCache.h/.cpp` ; `ModdingExtra.cpp` (démarrage en arrière-plan), `ModdingExtraBPLibrary.cpp` (`LoadTxt` depuis la mémoire, `SaveTxt`, vérification, statistiques cache/disque), `ModdingExtra.Build.cs` (`Json`) | couche de données étapes 2 et 3 : `Modules` chargé une fois en arrière-plan, cache compilé JSON par type avec champs et calque ; 2 031 fichiers en 0,14 s depuis le cache ; build `LifePlay_Build_5.5_DATA23` ; validation en jeu en attente | `git checkout DATA23-avant -- Plugins/ModdingExtra` ; ou option `-LPNoDataCache` |
|
||||
| CHG-077 | 2026-10-02 | O1b / DATA23 | `Config/DefaultGame.ini` : `+DirectoriesToNeverCook=(Path="/HairStrands")` retiré ; validation de la couche de données (CHG-076) : 2 658 textes servis par le cache, 0 écart, lecture 0,074 s pour la session | ERR-039 : erreurs `GroomComponent` au démarrage depuis O1 (module conservé par dépendance, contenu non cuit) ; vérifié : 0 erreur après reconstruction | remettre la ligne dans `DefaultGame.ini` |
|
||||
| CHG-078 | 2026-10-02 | DATA4b | nouveaux `Source/LifePlay/LPDataCompat.h/.cpp` (`WhatWhereToArray` en C++, vérification `-LPVerifyWhatWhere` contre la fonction Blueprint d'origine) et `Source/LifePlayEditorTools/LPFixWhatWhereCommandlet.h/.cpp` ; `Content/BPs/GI.uasset` : 2 appels de l'EventGraph rebranchés (diff : 22 lignes, uniquement les 2 nœuds et les liaisons de leurs voisins) | `WhatWhereToArray` = 94 % de `PopulateScenes` (3,4 s par passage) ; premier essai refusé par le contrôle intégré (broche `Target` non exclue), rien sauvegardé, commandlet corrigée ; build `LifePlay_Build_5.5_DATA4b` ; validation en jeu en attente | `Tools\Restore.ps1 -Lot DATA4b` |
|
||||
|
|
|
|||
187
Source/LifePlay/LPDataCompat.cpp
Normal file
187
Source/LifePlay/LPDataCompat.cpp
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// Portage UE 5.5 - voir LPDataCompat.h
|
||||
|
||||
#include "LPDataCompat.h"
|
||||
|
||||
#include "Kismet/KismetStringLibrary.h"
|
||||
#include "LifePlay.h"
|
||||
#include "LPStringCompat.h"
|
||||
#include "Misc/CommandLine.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
/** Champ chaine d'une structure Blueprint par son nom d'auteur (les noms internes portent un suffixe GUID). */
|
||||
const FStrProperty* FindStructString(const UStruct* Struct, const TCHAR* AuthoredName)
|
||||
{
|
||||
for (TFieldIterator<FStrProperty> It(Struct); It; ++It)
|
||||
{
|
||||
if (It->GetAuthoredName() == AuthoredName)
|
||||
{
|
||||
return *It;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** Valeurs du champ AuthoredName de chaque element du tableau de structures ArrayName de GI, dans l'ordre. */
|
||||
TArray<FString> ReadStructArrayField(const UObject* GI, const TCHAR* ArrayName, const TCHAR* AuthoredName)
|
||||
{
|
||||
TArray<FString> Values;
|
||||
const FArrayProperty* ArrayProp = GI ? CastField<FArrayProperty>(GI->GetClass()->FindPropertyByName(ArrayName)) : nullptr;
|
||||
const FStructProperty* Inner = ArrayProp ? CastField<FStructProperty>(ArrayProp->Inner) : nullptr;
|
||||
const FStrProperty* Field = Inner ? FindStructString(Inner->Struct, AuthoredName) : nullptr;
|
||||
if (!Field)
|
||||
{
|
||||
UE_LOG(LogLifePlay, Error, TEXT("WhatWhereToArray : champ %s.%s introuvable sur %s"), ArrayName, AuthoredName, *GetNameSafe(GI));
|
||||
return Values;
|
||||
}
|
||||
FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(GI));
|
||||
Values.Reserve(Helper.Num());
|
||||
for (int32 i = 0; i < Helper.Num(); ++i)
|
||||
{
|
||||
Values.Add(Field->GetPropertyValue_InContainer(Helper.GetRawPtr(i)));
|
||||
}
|
||||
return Values;
|
||||
}
|
||||
|
||||
/** GetArrayItem du Blueprint : valeur par defaut hors bornes. */
|
||||
FString ItemOrEmpty(const TArray<FString>& Array, int32 Index)
|
||||
{
|
||||
return Array.IsValidIndex(Index) ? Array[Index] : FString();
|
||||
}
|
||||
|
||||
/** SwitchString du Blueprint (insensible a la casse) : vrai si Value egale un des cas. */
|
||||
bool MatchesAnyCase(const FString& Value, std::initializer_list<const TCHAR*> Cases)
|
||||
{
|
||||
for (const TCHAR* Case : Cases)
|
||||
{
|
||||
if (UKismetStringLibrary::EqualEqual_StriStri(Value, Case))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FString> ULPDataCompat::ComputeWhatWhere(const UObject* GI, const FString& Line)
|
||||
{
|
||||
// Partie gauche / droite de la ligne, avec gestion des ':' supplementaires (rejoints par ':').
|
||||
const TArray<FString> Parts = ULPStringCompat::ParseIntoArray(Line, TEXT(":"), true);
|
||||
const bool bWhat = UKismetStringLibrary::EqualEqual_StriStri(ItemOrEmpty(Parts, 0), TEXT("WHAT"));
|
||||
FString SecondPart;
|
||||
if (Parts.Num() > 2)
|
||||
{
|
||||
TArray<FString> Dup = Parts;
|
||||
Dup.RemoveAt(0);
|
||||
SecondPart = UKismetStringLibrary::JoinStringArray(Dup, TEXT(":"));
|
||||
}
|
||||
else
|
||||
{
|
||||
SecondPart = ItemOrEmpty(Parts, 1);
|
||||
}
|
||||
|
||||
// ClearWhiteSpacesFromArray : Trim (debut) puis TrimTrailing, chaque element conserve.
|
||||
TArray<FString> Cleaned;
|
||||
for (const FString& Item : ULPStringCompat::ParseIntoArray(SecondPart, TEXT(","), true))
|
||||
{
|
||||
Cleaned.Add(UKismetStringLibrary::TrimTrailing(UKismetStringLibrary::Trim(Item)));
|
||||
}
|
||||
|
||||
if (!UKismetStringLibrary::EqualEqual_StriStri(ItemOrEmpty(Cleaned, 0), TEXT("all")))
|
||||
{
|
||||
return Cleaned;
|
||||
}
|
||||
|
||||
TArray<FString> FinalArray;
|
||||
if (bWhat)
|
||||
{
|
||||
for (const FString& ActionUID : ReadStructArrayField(GI, TEXT("ActionDetail"), TEXT("AmenityUID")))
|
||||
{
|
||||
if (MatchesAnyCase(ActionUID, { TEXT("sleep"), TEXT("sleep_hotel"), TEXT("nap"), TEXT("nap_hotel"), TEXT("masturbate"),
|
||||
TEXT("bathe"), TEXT("go_to_the_bathroom"), TEXT("check_on_my_family") }))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (ULPStringCompat::Contains(ActionUID, TEXT("_HARDCODE"), false, true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FinalArray.Add(ActionUID);
|
||||
}
|
||||
FinalArray.Remove(TEXT("sleep"));
|
||||
FinalArray.Remove(TEXT("Sleep"));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const FString& Amenity : ReadStructArrayField(GI, TEXT("AmenityAction"), TEXT("Amenity")))
|
||||
{
|
||||
if (!MatchesAnyCase(Amenity, { TEXT("sexwork"), TEXT("crime"), TEXT("modeling"), TEXT("prison") }))
|
||||
{
|
||||
FinalArray.Add(Amenity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Elements "-xxx" : retrait de "xxx" (partie droite du premier '-', recherche insensible a la casse).
|
||||
for (const FString& Item : Cleaned)
|
||||
{
|
||||
FString Left, Right;
|
||||
if (ULPStringCompat::Split(Item, TEXT("-"), Left, Right, ESearchCase::IgnoreCase, ESearchDir::FromStart))
|
||||
{
|
||||
FinalArray.Remove(Right);
|
||||
}
|
||||
}
|
||||
return FinalArray;
|
||||
}
|
||||
|
||||
void ULPDataCompat::WhatWhereToArray(UObject* Target, const FString& Line, TArray<FString>& Array)
|
||||
{
|
||||
Array = ComputeWhatWhere(Target, Line);
|
||||
|
||||
static const bool bVerify = FParse::Param(FCommandLine::Get(), TEXT("LPVerifyWhatWhere"));
|
||||
if (!bVerify || !Target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Verification : execution de la fonction Blueprint d'origine de GI et comparaison exacte (ordre et casse).
|
||||
UFunction* Original = Target->FindFunction(TEXT("WhatWhereToArray"));
|
||||
const FStrProperty* LineProp = Original ? CastField<FStrProperty>(Original->FindPropertyByName(TEXT("Line"))) : nullptr;
|
||||
const FArrayProperty* ArrayProp = Original ? CastField<FArrayProperty>(Original->FindPropertyByName(TEXT("Array"))) : nullptr;
|
||||
if (!LineProp || !ArrayProp)
|
||||
{
|
||||
UE_LOG(LogLifePlay, Error, TEXT("WhatWhereToArray : fonction Blueprint d'origine introuvable, verification impossible"));
|
||||
return;
|
||||
}
|
||||
uint8* Params = static_cast<uint8*>(FMemory_Alloca(Original->ParmsSize));
|
||||
FMemory::Memzero(Params, Original->ParmsSize);
|
||||
for (TFieldIterator<FProperty> It(Original); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
|
||||
{
|
||||
It->InitializeValue_InContainer(Params);
|
||||
}
|
||||
LineProp->SetPropertyValue_InContainer(Params, Line);
|
||||
Target->ProcessEvent(Original, Params);
|
||||
const TArray<FString>& Expected = *ArrayProp->ContainerPtrToValuePtr<TArray<FString>>(Params);
|
||||
|
||||
static int32 Calls = 0, Mismatches = 0;
|
||||
++Calls;
|
||||
bool bSame = Expected.Num() == Array.Num();
|
||||
for (int32 i = 0; bSame && i < Array.Num(); ++i)
|
||||
{
|
||||
bSame = Expected[i].Equals(Array[i], ESearchCase::CaseSensitive);
|
||||
}
|
||||
if (!bSame)
|
||||
{
|
||||
++Mismatches;
|
||||
UE_LOG(LogLifePlay, Error, TEXT("WhatWhereToArray ECART #%d | '%s' : Blueprint %d element(s) [%s], C++ %d element(s) [%s]"),
|
||||
Mismatches, *Line, Expected.Num(), *FString::Join(Expected, TEXT(",")).Left(300), Array.Num(), *FString::Join(Array, TEXT(",")).Left(300));
|
||||
}
|
||||
if (Calls % 500 == 0 || !bSame)
|
||||
{
|
||||
UE_LOG(LogLifePlay, Display, TEXT("WhatWhereToArray verification : %d appel(s), %d ecart(s)"), Calls, Mismatches);
|
||||
}
|
||||
for (TFieldIterator<FProperty> It(Original); It && It->HasAnyPropertyFlags(CPF_Parm); ++It)
|
||||
{
|
||||
It->DestroyValue_InContainer(Params);
|
||||
}
|
||||
}
|
||||
30
Source/LifePlay/LPDataCompat.h
Normal file
30
Source/LifePlay/LPDataCompat.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Portage UE 5.5 - couche de donnees, etape 4b (Docs/Migration/11_Audit_Optimisation.md §17.4).
|
||||
//
|
||||
// GI::WhatWhereToArray developpe les lignes "WHAT: ..." et "WHERE: ..." des en-tetes de scenes en listes d'actions ou de
|
||||
// lieux. Appelee 1 558 fois par passage de PopulateRaws, elle represente 94 % de PopulateScenes (3,4 s) : pour chaque
|
||||
// "all", le Blueprint parcourt GI.ActionDetail ou GI.AmenityAction en copiant chaque structure.
|
||||
//
|
||||
// Cette fonction reproduit le graphe Blueprint a l'identique (memes fonctions de chaine LPStringCompat / KismetString,
|
||||
// memes exclusions, memes doublons, meme ordre). Les deux appels de l'EventGraph de GI y sont rebranches par la
|
||||
// commandlet LPFixWhatWhere. Option -LPVerifyWhatWhere : chaque appel execute aussi la fonction Blueprint d'origine
|
||||
// (toujours presente dans GI) et journalise tout ecart.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "LPDataCompat.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class LIFEPLAY_API ULPDataCompat : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Equivalent C++ de GI::WhatWhereToArray. Target = instance de jeu (GI), implicite depuis GI. */
|
||||
UFUNCTION(BlueprintCallable, Category = "LifePlay|Compat", meta = (DefaultToSelf = "Target", HidePin = "Target"))
|
||||
static void WhatWhereToArray(UObject* Target, const FString& Line, TArray<FString>& Array);
|
||||
|
||||
/** Implementation pure (sans verification), exposee pour les tests. */
|
||||
static TArray<FString> ComputeWhatWhere(const UObject* GI, const FString& Line);
|
||||
};
|
||||
315
Source/LifePlayEditorTools/LPFixWhatWhereCommandlet.cpp
Normal file
315
Source/LifePlayEditorTools/LPFixWhatWhereCommandlet.cpp
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
// Portage UE 5.5 - voir LPFixWhatWhereCommandlet.h
|
||||
|
||||
#include "LPFixWhatWhereCommandlet.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_CallFunction.h"
|
||||
#include "Kismet2/BlueprintEditorUtils.h"
|
||||
#include "Kismet2/KismetEditorUtilities.h"
|
||||
#include "LPDataCompat.h"
|
||||
#include "Misc/PackageName.h"
|
||||
#include "UObject/Package.h"
|
||||
#include "UObject/SavePackage.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogLPFixWhatWhere, Log, All);
|
||||
|
||||
namespace
|
||||
{
|
||||
/** Extremites reliees a une broche, triees : "Noeud.Broche". */
|
||||
TArray<FString> Links(const UEdGraphPin* Pin)
|
||||
{
|
||||
TArray<FString> Out;
|
||||
if (Pin)
|
||||
{
|
||||
for (const UEdGraphPin* L : Pin->LinkedTo)
|
||||
{
|
||||
Out.Add(FString::Printf(TEXT("%s.%s"),
|
||||
L && L->GetOwningNode() ? *L->GetOwningNode()->GetName() : TEXT("?"), L ? *L->PinName.ToString() : TEXT("?")));
|
||||
}
|
||||
}
|
||||
Out.Sort();
|
||||
return Out;
|
||||
}
|
||||
|
||||
/** Etat comparable d'une broche : liaisons et valeur par defaut. */
|
||||
FString PinState(const UEdGraphPin* Pin)
|
||||
{
|
||||
return FString::Printf(TEXT("[%s] defaut='%s' objet=%s texte='%s'"),
|
||||
*FString::Join(Links(Pin), TEXT(", ")), *Pin->DefaultValue,
|
||||
Pin->DefaultObject ? *Pin->DefaultObject->GetPathName() : TEXT("None"), *Pin->DefaultTextValue.ToString());
|
||||
}
|
||||
|
||||
/** ULPDataCompat::WhatWhereToArray si le noeud appelle la fonction WhatWhereToArray de GI (appel sur self), sinon nullptr. */
|
||||
UFunction* ReplacementFor(const UK2Node_CallFunction* Node)
|
||||
{
|
||||
if (!Node || Node->FunctionReference.GetMemberName() != FName(TEXT("WhatWhereToArray")))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const UClass* Owner = Node->FunctionReference.GetMemberParentClass(Node->GetBlueprintClassFromNode());
|
||||
if (!Owner || (!Owner->GetName().StartsWith(TEXT("GI_C")) && !Owner->GetName().StartsWith(TEXT("SKEL_GI_C"))))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return ULPDataCompat::StaticClass()->FindFunctionByName(TEXT("WhatWhereToArray"));
|
||||
}
|
||||
|
||||
/** Broches ignorees dans les comparaisons : cible implicite (self de l'appel d'origine, Target cache du remplacant). */
|
||||
bool IsTargetPin(const UEdGraphPin* Pin)
|
||||
{
|
||||
return Pin && (Pin->PinName == UEdGraphSchema_K2::PN_Self || Pin->PinName == FName(TEXT("Target")));
|
||||
}
|
||||
|
||||
bool HasExecPins(const UEdGraphNode* Node)
|
||||
{
|
||||
for (const UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ULPFixWhatWhereCommandlet::ULPFixWhatWhereCommandlet()
|
||||
{
|
||||
IsClient = false;
|
||||
IsServer = false;
|
||||
IsEditor = true;
|
||||
LogToConsole = true;
|
||||
}
|
||||
|
||||
int32 ULPFixWhatWhereCommandlet::Main(const FString& Params)
|
||||
{
|
||||
const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase);
|
||||
|
||||
if (!ULPDataCompat::StaticClass()->FindFunctionByName(TEXT("WhatWhereToArray")))
|
||||
{
|
||||
UE_LOG(LogLPFixWhatWhere, Error, TEXT("ULPDataCompat::WhatWhereToArray introuvable."));
|
||||
return 1;
|
||||
}
|
||||
TMap<FName, int32> PerFunction;
|
||||
|
||||
UE_LOG(LogLPFixWhatWhere, Warning, TEXT("=== GI::WhatWhereToArray -> LPDataCompat::WhatWhereToArray%s ==="),
|
||||
bDryRun ? TEXT(" [DRY RUN]") : TEXT(""));
|
||||
|
||||
FAssetRegistryModule& AssetRegistryModule =
|
||||
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
||||
AssetRegistryModule.Get().SearchAllAssets(true);
|
||||
|
||||
TArray<FAssetData> BlueprintAssets;
|
||||
// Sous-classes incluses : couvre les UWidgetBlueprint.
|
||||
AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true);
|
||||
|
||||
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
||||
|
||||
int32 NodesFound = 0;
|
||||
int32 NodesFixed = 0;
|
||||
int32 Unhandled = 0;
|
||||
int32 BlueprintsModified = 0;
|
||||
int32 SaveFailures = 0;
|
||||
|
||||
for (const FAssetData& AssetData : BlueprintAssets)
|
||||
{
|
||||
if (AssetData.PackageName != FName(TEXT("/Game/BPs/GI")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UBlueprint* Blueprint = Cast<UBlueprint>(AssetData.GetAsset());
|
||||
if (!Blueprint)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TArray<UEdGraph*> Graphs;
|
||||
Blueprint->GetAllGraphs(Graphs);
|
||||
|
||||
int32 FixedHere = 0;
|
||||
int32 FailedHere = 0;
|
||||
|
||||
for (UEdGraph* Graph : Graphs)
|
||||
{
|
||||
if (!Graph)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Copie : on ajoute et on detruit des noeuds pendant le parcours.
|
||||
TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
|
||||
for (UEdGraphNode* GraphNode : Snapshot)
|
||||
{
|
||||
UK2Node_CallFunction* OldNode = Cast<UK2Node_CallFunction>(GraphNode);
|
||||
UFunction* NewFunction = ReplacementFor(OldNode);
|
||||
if (!NewFunction)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
++NodesFound;
|
||||
++PerFunction.FindOrAdd(NewFunction->GetFName());
|
||||
|
||||
UE_LOG(LogLPFixWhatWhere, Warning, TEXT(" %s :: %s -> %s %s (%d, %d)"),
|
||||
*Blueprint->GetName(), *Graph->GetName(), *OldNode->GetName(), *NewFunction->GetName(), OldNode->NodePosX, OldNode->NodePosY);
|
||||
|
||||
// Etat avant, par nom de broche (self exclu : cible implicite de la bibliotheque).
|
||||
TMap<FName, FString> Before;
|
||||
for (const UEdGraphPin* Pin : OldNode->Pins)
|
||||
{
|
||||
if (Pin && !IsTargetPin(Pin))
|
||||
{
|
||||
Before.Add(Pin->PinName, PinState(Pin));
|
||||
}
|
||||
}
|
||||
const UEdGraphPin* OldSelf = OldNode->FindPin(UEdGraphSchema_K2::PN_Self, EGPD_Input);
|
||||
|
||||
// Appel impur (broches d'execution) attendu des deux cotes ; seule une cible self reliee est refusee.
|
||||
if (!HasExecPins(OldNode) || (OldSelf && OldSelf->LinkedTo.Num() > 0))
|
||||
{
|
||||
++Unhandled;
|
||||
++FailedHere;
|
||||
UE_LOG(LogLPFixWhatWhere, Error, TEXT(" cas non pris en charge (%s) : noeud laisse en l'etat."),
|
||||
!HasExecPins(OldNode) ? TEXT("noeud pur inattendu") : TEXT("cible self reliee"));
|
||||
continue;
|
||||
}
|
||||
if (bDryRun)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UK2Node_CallFunction* NewNode = NewObject<UK2Node_CallFunction>(Graph);
|
||||
NewNode->SetFromFunction(NewFunction);
|
||||
NewNode->NodePosX = OldNode->NodePosX;
|
||||
NewNode->NodePosY = OldNode->NodePosY;
|
||||
NewNode->CreateNewGuid();
|
||||
NewNode->PostPlacedNewNode();
|
||||
NewNode->AllocateDefaultPins();
|
||||
Graph->Modify();
|
||||
Graph->AddNode(NewNode, false, false);
|
||||
|
||||
bool bOk = true;
|
||||
for (UEdGraphPin* OldPin : OldNode->Pins)
|
||||
{
|
||||
if (!OldPin || IsTargetPin(OldPin))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UEdGraphPin* NewPin = NewNode->FindPin(OldPin->PinName, OldPin->Direction);
|
||||
if (!NewPin)
|
||||
{
|
||||
bOk = false;
|
||||
UE_LOG(LogLPFixWhatWhere, Error, TEXT(" broche %s sans equivalent."), *OldPin->PinName.ToString());
|
||||
break;
|
||||
}
|
||||
NewPin->DefaultValue = OldPin->DefaultValue;
|
||||
NewPin->DefaultObject = OldPin->DefaultObject;
|
||||
NewPin->DefaultTextValue = OldPin->DefaultTextValue;
|
||||
if (OldPin->LinkedTo.Num() > 0
|
||||
&& Schema->MovePinLinks(*OldPin, *NewPin).Response == CONNECT_RESPONSE_DISALLOW)
|
||||
{
|
||||
bOk = false;
|
||||
UE_LOG(LogLPFixWhatWhere, Error, TEXT(" liaison refusee sur %s."), *OldPin->PinName.ToString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Meme ensemble de broches, memes liaisons (les noms des voisins ne changent pas), memes valeurs.
|
||||
if (bOk)
|
||||
{
|
||||
int32 Compared = 0;
|
||||
for (const UEdGraphPin* NewPin : NewNode->Pins)
|
||||
{
|
||||
if (!NewPin || IsTargetPin(NewPin))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FString* Expected = Before.Find(NewPin->PinName);
|
||||
if (!Expected || *Expected != PinState(NewPin))
|
||||
{
|
||||
bOk = false;
|
||||
UE_LOG(LogLPFixWhatWhere, Error, TEXT(" apres : %s %s (attendu %s)"),
|
||||
*NewPin->PinName.ToString(), *PinState(NewPin), Expected ? **Expected : TEXT("<absent>"));
|
||||
}
|
||||
++Compared;
|
||||
}
|
||||
if (Compared != Before.Num())
|
||||
{
|
||||
bOk = false;
|
||||
UE_LOG(LogLPFixWhatWhere, Error, TEXT(" %d broche(s) apres, %d avant."), Compared, Before.Num());
|
||||
}
|
||||
}
|
||||
|
||||
if (!bOk)
|
||||
{
|
||||
// Le Blueprint ne sera pas sauvegarde : le paquet sur disque reste intact.
|
||||
++Unhandled;
|
||||
++FailedHere;
|
||||
UE_LOG(LogLPFixWhatWhere, Error, TEXT(" ecart : Blueprint non sauvegarde."));
|
||||
continue;
|
||||
}
|
||||
|
||||
OldNode->DestroyNode();
|
||||
++NodesFixed;
|
||||
++FixedHere;
|
||||
}
|
||||
}
|
||||
|
||||
if (FailedHere > 0)
|
||||
{
|
||||
UE_LOG(LogLPFixWhatWhere, Error, TEXT(" -> %s : %d noeud(s) en ecart, Blueprint NON sauvegarde."),
|
||||
*Blueprint->GetName(), FailedHere);
|
||||
continue;
|
||||
}
|
||||
if (FixedHere == 0 || bDryRun)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
++BlueprintsModified;
|
||||
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
|
||||
FKismetEditorUtilities::CompileBlueprint(Blueprint);
|
||||
|
||||
UPackage* Package = Blueprint->GetOutermost();
|
||||
FString Filename;
|
||||
if (!FPackageName::TryConvertLongPackageNameToFilename(
|
||||
Package->GetName(), Filename, FPackageName::GetAssetPackageExtension()))
|
||||
{
|
||||
++SaveFailures;
|
||||
UE_LOG(LogLPFixWhatWhere, 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(LogLPFixWhatWhere, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere);
|
||||
}
|
||||
else
|
||||
{
|
||||
++SaveFailures;
|
||||
UE_LOG(LogLPFixWhatWhere, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
for (const TPair<FName, int32>& It : PerFunction)
|
||||
{
|
||||
UE_LOG(LogLPFixWhatWhere, Warning, TEXT(" %s : %d noeud(s)"), *It.Key.ToString(), It.Value);
|
||||
}
|
||||
UE_LOG(LogLPFixWhatWhere, Warning,
|
||||
TEXT("=== Termine : %d noeud(s) trouve(s), %d remplace(s), %d Blueprint(s) modifie(s), %d non traite(s), %d echec(s) de sauvegarde. ==="),
|
||||
NodesFound, NodesFixed, BlueprintsModified, Unhandled, SaveFailures);
|
||||
|
||||
return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0;
|
||||
}
|
||||
24
Source/LifePlayEditorTools/LPFixWhatWhereCommandlet.h
Normal file
24
Source/LifePlayEditorTools/LPFixWhatWhereCommandlet.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Portage UE 5.5 - couche de donnees, etape 4b. Voir Source/LifePlay/LPDataCompat.h.
|
||||
//
|
||||
// Rebranche les appels de GI a sa fonction Blueprint WhatWhereToArray (2 appels dans l'EventGraph, PopulateScenes) sur
|
||||
// ULPDataCompat::WhatWhereToArray (memes broches Line et Array, broches d'execution conservees, cible implicite GI).
|
||||
// Controle integre (repris de LPFixStringSearch) : l'etat de chaque broche est compare avant et apres ; tout ecart :
|
||||
// noeud laisse en l'etat, Blueprint non sauvegarde. La fonction Blueprint d'origine reste dans GI (verification).
|
||||
//
|
||||
// Usage :
|
||||
// UnrealEditor-Cmd.exe <projet> -run=LPFixWhatWhere [-DryRun]
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Commandlets/Commandlet.h"
|
||||
#include "LPFixWhatWhereCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class ULPFixWhatWhereCommandlet : public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ULPFixWhatWhereCommandlet();
|
||||
virtual int32 Main(const FString& Params) override;
|
||||
};
|
||||
Loading…
Reference in a new issue