L4-CX-B08 : ParseIntoArray au comportement UE 4.27 (cause d'ERR-027)
LPStringCompat::ParseIntoArray (algorithme 4.27 : chaine vide -> [], separateur sensible a la casse) ; commandlet LPFixParseIntoArray : 114 noeuds rebranches dans 17 Blueprints, 0 ecart de liaison (controle integre), 0 broche orpheline, CompileAllBlueprints 0 erreur. LPDumpFunction : option -Exec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d4eea7397b
commit
0d703d47a7
22 changed files with 314 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -325,3 +325,20 @@ entre 4.27 et 5.5 ; la divergence est dans le moteur.
|
|||
|
||||
Preuves : `Saved/Logs/portage/L5c_partie_utilisateur2_20260929.log`, `etat/L5-diag_dump55exec_GI_GeneratePerson.txt`,
|
||||
`etat/L5-diag_dump55exec_GI_GenerateAndApplyStrings.txt`.
|
||||
|
||||
### Sous-lot L4-CX-B08 — `ParseIntoArray` au comportement UE 4.27 (ERR-027) — validé statiquement
|
||||
|
||||
Tag d'entrée `L4-CX-B08-avant`. Sauvegarde : `D:\Game\LifePlay_Backup_5.5\L4-CX-B08\` (17 Blueprints ; 4 fichiers
|
||||
source créés, retirés par `Restore.ps1` en cas de retour).
|
||||
|
||||
| Étape | Résultat | Preuve (`etat/`) |
|
||||
|---|---|---|
|
||||
| Fonction de remplacement | `ULPStringCompat::ParseIntoArray` : transcription de l'algorithme 4.27 (chaîne vide → `[]` ; séparateur sensible à la casse), même signature que `UKismetStringLibrary::ParseIntoArray` | `Source/LifePlay/LPStringCompat.*` |
|
||||
| Simulation | 114 nœuds, 17 Blueprints (GI 73, Person 10, Room 7, StreetMap_Static 4, 13 widgets) ; rien écrit | `L4-CX-B08_dryrun.txt` |
|
||||
| Application | 114 rebranchés, 0 écart de liaison (contrôle intégré : liaisons et valeurs par défaut de chaque broche identiques avant/après), 0 échec | `L4-CX-B08_application.txt` |
|
||||
| Contrôles | 0 broche orpheline ; `CompileAllBlueprints` 0 erreur, 95 avertissements (inchangé), 0 échec de chargement | résumés `L4-CX-B08_*` |
|
||||
|
||||
| ID | Date | Lot | Fichiers | Raison | Retour arrière |
|
||||
|---|---|---|---|---|---|
|
||||
| CHG-029 | 2026-09-29 | L4-CX-B08 | `Source/LifePlay/LPStringCompat.h/.cpp`, `Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.h/.cpp` (nouveaux) ; 17 Blueprints | `ParseIntoArray` UE5 renvoie `[""]` pour une chaîne vide (UE4 : `[]`) : `generatePerson()` sans argument chargeait les 141 presets | `Tools\Restore.ps1 -Lot L4-CX-B08` ou `git checkout L4-CX-B08-avant -- Content Source` |
|
||||
| CHG-030 | 2026-09-29 | L5 | `Source/LifePlayEditorTools/LPDumpFunctionCommandlet.cpp` | option `-Exec` (broches d'exécution) | `Tools\Restore.ps1 -Lot L5-dumpexec` |
|
||||
|
|
|
|||
27
Source/LifePlay/LPStringCompat.cpp
Normal file
27
Source/LifePlay/LPStringCompat.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Portage UE 5.5 - voir LPStringCompat.h
|
||||
|
||||
#include "LPStringCompat.h"
|
||||
|
||||
TArray<FString> ULPStringCompat::ParseIntoArray(const FString& SourceString, const FString& Delimiter, const bool CullEmptyStrings)
|
||||
{
|
||||
// Transcription de FString::ParseIntoArray, UE 4.27 (String.cpp, lignes 940-963).
|
||||
TArray<FString> OutArray;
|
||||
const TCHAR* Start = *SourceString;
|
||||
const int32 DelimLength = Delimiter.Len();
|
||||
if (Start && *Start != TEXT('\0') && DelimLength)
|
||||
{
|
||||
while (const TCHAR* At = FCString::Strstr(Start, *Delimiter))
|
||||
{
|
||||
if (!CullEmptyStrings || At - Start)
|
||||
{
|
||||
OutArray.Emplace(UE_PTRDIFF_TO_INT32(At - Start), Start);
|
||||
}
|
||||
Start = At + DelimLength;
|
||||
}
|
||||
if (!CullEmptyStrings || *Start)
|
||||
{
|
||||
OutArray.Emplace(Start);
|
||||
}
|
||||
}
|
||||
return OutArray;
|
||||
}
|
||||
32
Source/LifePlay/LPStringCompat.h
Normal file
32
Source/LifePlay/LPStringCompat.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Portage UE 5.5 (2026.9 beta) - correctif CX-B08 (ERR-027).
|
||||
//
|
||||
// UE 5.x a reecrit FString::ParseIntoArray sur UE::String::ParseTokens. Deux comportements
|
||||
// ont change par rapport a UE 4.27, sans aucun avertissement :
|
||||
// 1. chaine source vide, CullEmpty=false : UE4 renvoie [] ; UE5 renvoie [""] ;
|
||||
// 2. recherche du separateur : UE4 respecte la casse (FCString::Strstr) ; UE5 l'ignore.
|
||||
//
|
||||
// Le cas 1 casse generatePerson() sans argument : "()" donne Presets=[""], GeneratePerson
|
||||
// charge alors les 141 presets (suffixe "" + ".lpcharacter") et finit sur un corps legacy
|
||||
// jamais charge -> personnage invisible et devetu.
|
||||
//
|
||||
// ULPStringCompat::ParseIntoArray reproduit a l'identique l'algorithme 4.27 (Source 4.27,
|
||||
// Runtime/Core/Private/Containers/String.cpp, FString::ParseIntoArray). Ses parametres et
|
||||
// ses valeurs par defaut sont ceux de UKismetStringLibrary::ParseIntoArray, pour que la
|
||||
// commandlet LPFixParseIntoArray puisse rebrancher les noeuds existants sans perdre de liaison.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "LPStringCompat.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class LIFEPLAY_API ULPStringCompat : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** ParseIntoArray avec le comportement exact de UE 4.27 (voir en-tete du fichier). */
|
||||
UFUNCTION(BlueprintPure, Category = "Utilities|String", meta = (DisplayName = "Parse Into Array (UE4)"))
|
||||
static TArray<FString> ParseIntoArray(const FString& SourceString, const FString& Delimiter = FString(TEXT(" ")), const bool CullEmptyStrings = true);
|
||||
};
|
||||
210
Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.cpp
Normal file
210
Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.cpp
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
// Portage UE 5.5 - voir LPFixParseIntoArrayCommandlet.h
|
||||
|
||||
#include "LPFixParseIntoArrayCommandlet.h"
|
||||
|
||||
#include "AssetRegistry/AssetData.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "K2Node_CallFunction.h"
|
||||
#include "Kismet/KismetStringLibrary.h"
|
||||
#include "Kismet2/BlueprintEditorUtils.h"
|
||||
#include "Kismet2/KismetEditorUtilities.h"
|
||||
#include "LPStringCompat.h"
|
||||
#include "Misc/PackageName.h"
|
||||
#include "UObject/Package.h"
|
||||
#include "UObject/SavePackage.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogLPFixParse, Log, All);
|
||||
|
||||
namespace
|
||||
{
|
||||
const FName ParseIntoArrayName(TEXT("ParseIntoArray"));
|
||||
|
||||
/** Liaisons et valeur par defaut de chaque broche, par nom : l'empreinte a retrouver. */
|
||||
TMap<FName, FString> Topology(const UEdGraphNode* Node)
|
||||
{
|
||||
TMap<FName, FString> Out;
|
||||
for (const UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (!Pin || Pin->bOrphanedPin)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
TArray<FString> Links;
|
||||
for (const UEdGraphPin* L : Pin->LinkedTo)
|
||||
{
|
||||
Links.Add(FString::Printf(TEXT("%s.%s"),
|
||||
L && L->GetOwningNode() ? *L->GetOwningNode()->GetName() : TEXT("?"),
|
||||
L ? *L->PinName.ToString() : TEXT("?")));
|
||||
}
|
||||
Links.Sort();
|
||||
Out.Add(Pin->PinName, FString::Printf(TEXT("[%s] defaut='%s'"), *FString::Join(Links, TEXT(", ")), *Pin->DefaultValue));
|
||||
}
|
||||
return Out;
|
||||
}
|
||||
}
|
||||
|
||||
ULPFixParseIntoArrayCommandlet::ULPFixParseIntoArrayCommandlet()
|
||||
{
|
||||
IsClient = false;
|
||||
IsServer = false;
|
||||
IsEditor = true;
|
||||
LogToConsole = true;
|
||||
}
|
||||
|
||||
int32 ULPFixParseIntoArrayCommandlet::Main(const FString& Params)
|
||||
{
|
||||
const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase);
|
||||
|
||||
UFunction* OldFunction = UKismetStringLibrary::StaticClass()->FindFunctionByName(ParseIntoArrayName);
|
||||
UFunction* NewFunction = ULPStringCompat::StaticClass()->FindFunctionByName(ParseIntoArrayName);
|
||||
if (!OldFunction || !NewFunction)
|
||||
{
|
||||
UE_LOG(LogLPFixParse, Error, TEXT("Fonction introuvable (ancienne=%s, nouvelle=%s)."),
|
||||
OldFunction ? TEXT("ok") : TEXT("NULL"), NewFunction ? TEXT("ok") : TEXT("NULL"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
UE_LOG(LogLPFixParse, Warning, TEXT("=== KismetStringLibrary::ParseIntoArray -> LPStringCompat::ParseIntoArray%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 NodesFixed = 0;
|
||||
int32 Unhandled = 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 FixedHere = 0;
|
||||
int32 FailedHere = 0;
|
||||
|
||||
for (UEdGraph* Graph : Graphs)
|
||||
{
|
||||
if (!Graph)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
|
||||
for (UEdGraphNode* GraphNode : Snapshot)
|
||||
{
|
||||
UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(GraphNode);
|
||||
if (!CallNode || CallNode->GetTargetFunction() != OldFunction)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
++NodesFound;
|
||||
const TMap<FName, FString> Before = Topology(CallNode);
|
||||
UE_LOG(LogLPFixParse, Warning, TEXT(" %s :: %s -> %s | Delimiter %s | CullEmptyStrings %s"),
|
||||
*Blueprint->GetName(), *Graph->GetName(), *CallNode->GetName(),
|
||||
*Before.FindRef(TEXT("Delimiter")), *Before.FindRef(TEXT("CullEmptyStrings")));
|
||||
|
||||
if (bDryRun)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CallNode->Modify();
|
||||
CallNode->SetFromFunction(NewFunction);
|
||||
CallNode->ReconstructNode();
|
||||
|
||||
const TMap<FName, FString> After = Topology(CallNode);
|
||||
bool bSame = Before.Num() == After.Num();
|
||||
for (const TPair<FName, FString>& It : Before)
|
||||
{
|
||||
const FString* A = After.Find(It.Key);
|
||||
if (!A || *A != It.Value)
|
||||
{
|
||||
bSame = false;
|
||||
UE_LOG(LogLPFixParse, Error, TEXT(" broche %s : avant %s | apres %s"),
|
||||
*It.Key.ToString(), *It.Value, A ? **A : TEXT("(absente)"));
|
||||
}
|
||||
}
|
||||
if (bSame && CallNode->GetTargetFunction() == NewFunction)
|
||||
{
|
||||
++NodesFixed;
|
||||
++FixedHere;
|
||||
}
|
||||
else
|
||||
{
|
||||
++Unhandled;
|
||||
++FailedHere;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FailedHere > 0)
|
||||
{
|
||||
// Rien n'est sauvegarde : le paquet reste tel qu'il est sur disque.
|
||||
UE_LOG(LogLPFixParse, Error, TEXT(" -> %s : %d noeud(s) en ecart, Blueprint NON sauvegarde."),
|
||||
*Blueprint->GetName(), FailedHere);
|
||||
continue;
|
||||
}
|
||||
|
||||
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(LogLPFixParse, 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(LogLPFixParse, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere);
|
||||
}
|
||||
else
|
||||
{
|
||||
++SaveFailures;
|
||||
UE_LOG(LogLPFixParse, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogLPFixParse, Warning,
|
||||
TEXT("=== Termine : %d noeud(s) trouve(s), %d rebranche(s), %d Blueprint(s) modifie(s), %d en ecart, %d echec(s) de sauvegarde. ==="),
|
||||
NodesFound, NodesFixed, BlueprintsModified, Unhandled, SaveFailures);
|
||||
|
||||
return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0;
|
||||
}
|
||||
28
Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.h
Normal file
28
Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Portage UE 5.5 - correctif CX-B08 (ERR-027). Voir Source/LifePlay/LPStringCompat.h.
|
||||
//
|
||||
// Rebranche chaque noeud KismetStringLibrary::ParseIntoArray des Blueprints de /Game vers
|
||||
// LPStringCompat::ParseIntoArray (comportement UE 4.27). Memes noms de broches : le
|
||||
// rebranchement se limite a un changement de reference de fonction suivi d'un ReconstructNode.
|
||||
//
|
||||
// Controle integre : avant modification, les liaisons et valeurs par defaut de chaque broche
|
||||
// sont relevees ; apres reconstruction, elles doivent etre retrouvees a l'identique. Tout
|
||||
// ecart compte le noeud comme non traite et le Blueprint n'est pas sauvegarde.
|
||||
//
|
||||
// Usage :
|
||||
// UnrealEditor-Cmd.exe <projet> -run=LPFixParseIntoArray [-DryRun]
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Commandlets/Commandlet.h"
|
||||
#include "LPFixParseIntoArrayCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class ULPFixParseIntoArrayCommandlet : public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ULPFixParseIntoArrayCommandlet();
|
||||
virtual int32 Main(const FString& Params) override;
|
||||
};
|
||||
Loading…
Reference in a new issue