L7i (CX-B12) : Contains/Split/FindSubstring avec motif vide au comportement 4.27 (385 noeuds, 28 Blueprints) ; ERR-005
FString::Find("" , IgnoreCase, FromStart) renvoie 0 en UE5 et INDEX_NONE en 4.27 : Contains(x, "") devenu vrai.
Test 3120 cas (transcription 4.27) : 0 ecart ; diff independant 385/385 ; compilation et cuisson 0 avertissement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
361581ff85
commit
8015f182b9
40 changed files with 754 additions and 3 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.
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.
|
|
@ -43,6 +43,7 @@ exporté dans `Docs/Migration/correctifs/` pour relecture.
|
|||
| CX-B09 | `LPFixButtonPadding` (nouveau) | 430 `UButton`, 46 Widget Blueprints | marges des boutons remises au défaut UE 4.27 (Normal 2,2,2,2 ; Pressed 2,3,2,1), composante par composante | le style par défaut de `UButton` est passé à 12 px horizontaux en UE5 (`FUMGCoreStyle`) ; les assets 4.27 ne stockent pas la marge par défaut : cause d'ERR-034 (barre de temps décalée) | 1 | **réimporté** (CHG-033), validé en jeu |
|
||||
| CX-B10 | `LPFixSkeletalMeshGet` (nouveau) | 56 nœuds `Get SkeletalMesh`, 8 Blueprints (`Person` 28, `GI` 17, `CharGen` 3, `Inventory` 3, `Hairstyle` 2, `CharGenSlider`, `Cursor`, `Inventory_Cloth` 1) | remplacés par `LPMeshCompat::GetSkeletalMesh(Component)` = `Cast<USkeletalMesh>(GetSkinnedAsset())`, la sémantique exacte du getter déprécié, valable pour tout `USkinnedMeshComponent` | R-03 refait (L7a) ; remplace CX-B05 rejeté | 1 | **réimporté** (CHG-036), validé en jeu |
|
||||
| CX-B11 | `LPFixNavRandomPoint` (nouveau) | 6 nœuds `GetRandomPointInNavigableRadius` (`Person` 3, `Room` 2, `AI` 1) | remplacés par `LPNavigationHelpers::GetRandomLocationInNavigableRadius` : nœud pur, mêmes broches, même corps que la fonction dépréciée (appel de `K2_GetRandomLocationInNavigableRadius`, identique en 4.27 et 5.5) ; liaisons et valeurs par défaut reportées broche par broche | R-04 (L7b) : 6 derniers avertissements de compilation ; le remplaçant moteur est impur (broches d'exécution), d'où l'enrobage pur | 1 | **réimporté** (CHG-037), retiré à tort (CHG-038) puis réappliqué (CHG-039) ; validé en jeu |
|
||||
| CX-B12 | `LPFixStringSearch` (nouveau) | 385 nœuds `Contains` (160), `Split` (222), `FindSubstring` (3), 28 Blueprints (`GI` 240, `Person` 35, `Room` 16, `CharGen` 14…) | rebranchés sur `LPStringCompat::Contains/Split/FindSubstring` : mêmes broches ; sous-chaîne vide en mode insensible à la casse depuis le début → « non trouvé » comme en 4.27 (UE5 : trouvé en position 0) | ERR-005 (`getBeautyValue` ×38 : `filterMorphList` sans étiquette laissait passer tous les morphs) et toute recherche de texte avec un motif vide | 1 | **réimporté** (CHG-042) ; validation en jeu en attente |
|
||||
| CX-B07 | `LPResavePackages` (nouveau) | 239 paquets UE4 restants (`UI` 147, `Structs` 57, `BPs` 22, `Render` 11, `Characters` 1, `Scenes` 1) | format UE5, contenu inchangé | EX-03 ; T-STAT-01 | 6 | **réimporté** (CHG-035), diff avant/après neutre ; validation en jeu en attente |
|
||||
|
||||
## 3. Protocole de réimport sous contrôle renforcé
|
||||
|
|
|
|||
|
|
@ -657,3 +657,36 @@ Suite L7i (2026-10-01) :
|
|||
(redémarrages : `CheckingSceneIndex` remis à -1 et liste remélangée à chaque changement de `CurrentAmenity`).
|
||||
Les sessions comparées n'ont pas suivi le même parcours (déplacements, accélération du temps) : une comparaison
|
||||
A/B sur un parcours identique est nécessaire avant de conclure à une régression.
|
||||
|
||||
A/B à parcours identique (2026-10-01, nouvelle partie, vitesse normale, maison puis travail ; 4.27 47 min,
|
||||
`AB427_20261001.log` ; 5.5 33 min, `AB55_20261001.log`) :
|
||||
|
||||
| Par minute | 4.27 | 5.5 |
|
||||
|---|---|---|
|
||||
| Vérifications de scènes | 626 | 614 |
|
||||
| `AI` introuvable (`getActorVar`…) | 0 | 0 |
|
||||
| Variables `Person` vides | 124 | 123 |
|
||||
| `getBeautyValue` (`Floats` vide) | 1,3 | 52 |
|
||||
|
||||
Les erreurs `AI` dépendaient du rythme de jeu (accélération du temps) : absentes à parcours égal, classées comme
|
||||
préexistantes (même mécanisme en 4.27). Seul écart réel : `getBeautyValue`.
|
||||
|
||||
Cause (ERR-005) : `BeautyStandards` est indexé par forme (`round`, `oval`…, fichier
|
||||
`Modules/beauty_standards_default.txt` identique dans les deux versions) ; la première recherche par nom de morph
|
||||
échoue normalement (62 avertissements en 4.27 aussi). L'écart vient du nombre d'appels : `filterMorphList` est
|
||||
appelé sans étiquette et teste `Contains(tags, "")`. Moteur : `FString::Find` 4.27 → `FCString::Stristr`, qui
|
||||
cherche le caractère nul et ne trouve rien ; 5.5 → `TCString::Strnistr`, qui renvoie le début de la chaîne. Les
|
||||
autres modes (sensible à la casse, depuis la fin) et `StartsWith`, `EndsWith`, `Replace` sont identiques.
|
||||
|
||||
| CHG | Date | Lot | Fichiers | Raison | Retour arrière |
|
||||
|---|---|---|---|---|---|
|
||||
| CHG-042 | 2026-10-01 | L7i (CX-B12) | `Source/LifePlay/LPStringCompat.h/.cpp` (`FindUE4`, `Contains`, `FindSubstring`, `Split`) ; `Source/LifePlayEditorTools/LPFixStringSearchCommandlet`, `LPTestStringCompatCommandlet`, `LPDumpCDOCommandlet` (nouveaux) ; `Tools/DiffRemplacementNoeuds.py` (profil `CX-B12`, broches absentes des deux côtés) ; 28 Blueprints (`etat/L7i_CX-B12_assets.txt`) | ERR-005 : recherche de texte avec motif vide | `Tools\Restore.ps1 -Lot L7i-CX-B12` ; `git checkout L7i-CX-B12-avant -- Source Tools` |
|
||||
|
||||
| Contrôle | Résultat | Preuve |
|
||||
|---|---|---|
|
||||
| Test `LPTestStringCompat` : transcription littérale de `FString::Find` 4.27 contre `LPStringCompat` et contre le moteur 5.5 | 3 120 cas : compat = 4.27 dans 100 % des cas ; moteur 5.5 ≠ 4.27 dans 60 cas, tous « sous-chaîne vide, insensible à la casse, depuis le début » | `Saved/Logs/portage/L7i_LPTestStringCompat_*.log` |
|
||||
| Simulation | 385 nœuds (`Contains` 160, `Split` 222, `FindSubstring` 3), 28 Blueprints, 0 cas non pris en charge | `etat/L7i_LPFixStringSearch_20261001-191027.txt` |
|
||||
| Application (contrôle intégré) | 385/385 remplacés, 28 Blueprints sauvegardés, 0 écart | `etat/L7i_LPFixStringSearch_20261001-191311.txt` |
|
||||
| Diff indépendant (`DiffRemplacementNoeuds.py … CX-B12`) | 385 paires, liaisons et valeurs par défaut identiques ; 0 autre objet modifié ; profils L7a (56/56) et L7b (6/6) inchangés | `Saved/Logs/portage/L7i_CX-B12_assets_{avant,apres}.txt` |
|
||||
| `CompileAllBlueprints` | 0 erreur, 0 avertissement | `etat/L7i_CompileAllBlueprints_20261001-191653.txt` |
|
||||
| Build empaquetée `LifePlay_Build_5.5_L7i_B12` | cuisson 0 erreur, 0 avertissement (164 paquets recuits) ; T-PKG-01 : 8561 `.uasset` | `Saved/Logs/portage/L7i_CX-B12_BuildCookRun.log` |
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
| ERR-028 | `clearVariable` : index 0 sur tableau `Person` vide de `Variable` (via `GeneratePerson`, `getRelative`, `GetSpecific`) | partie utilisateur L4b | 211 | **bruit** : présent à l'identique en 4.27 — piste écartée | — | accepté |
|
||||
| ERR-003 | `Attempted to access index 0 from array Float_… of length 0` dans `GI::ReturnStatIfNeeded` (variable DSL `Float` vide) | build 5.5 Development | 2 (4.27 : 0) | haute | L5 | ouvert |
|
||||
| ERR-004 | `index N from CallFunc_ParseIntoArray_ReturnValue of length N` (`GI` EventGraph) | PIE 09-28 | 6 510 | moyenne | L7b | ouvert — origine localisée (L3) : `GI.populateClothes4` appelée par `Person.ReceiveBeginPlay` |
|
||||
| ERR-005 | `getBeautyValue` : `Floats_*` index hors bornes | PIE 09-28 | 1 297 | moyenne | L7b | ouvert |
|
||||
| ERR-005 | `getBeautyValue` : `Floats_*` index hors bornes | PIE 09-28 ; A/B 2026-10-01 : 0,08/perso en 4.27 contre 3,0 en 5.5 | 1 297 | moyenne | L7i | **corrigé (CHG-042, CX-B12)** — cause : `FString::Find` avec sous-chaîne vide (insensible à la casse, depuis le début) renvoie 0 en UE5 et « non trouvé » en 4.27 ; `Contains(x, "")` devenu vrai : `filterMorphList` appelé sans étiquette ne filtrait plus. 385 nœuds `Contains`/`Split`/`FindSubstring` rebranchés sur `LPStringCompat` ; validation en jeu en attente |
|
||||
| ERR-006 | `Accès à None` `NearVec` dans `Person::standIdle` | PIE 09-28 | 273 | moyenne | L7k | ouvert |
|
||||
| ERR-007 | `Accès à None` `ContactMenu` dans `UpdateDailyPose` | PIE 09-28 | 257 | moyenne | L7k | ouvert |
|
||||
| ERR-008 | `CallFunc_Array_Get_Item` à None dans `GI` | PIE 09-28 | 229 | moyenne | L5 | ouvert |
|
||||
|
|
|
|||
|
|
@ -25,3 +25,39 @@ TArray<FString> ULPStringCompat::ParseIntoArray(const FString& SourceString, con
|
|||
}
|
||||
return OutArray;
|
||||
}
|
||||
|
||||
int32 ULPStringCompat::FindUE4(const FString& Str, const FString& SubStr, ESearchCase::Type SearchCase, ESearchDir::Type SearchDir, int32 StartPosition)
|
||||
{
|
||||
// UE 4.27 : FCString::Stristr(Start, TEXT("")) ne trouve jamais rien (il cherche nul final).
|
||||
if (SubStr.IsEmpty() && SearchCase == ESearchCase::IgnoreCase && SearchDir == ESearchDir::FromStart)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return Str.Find(SubStr, SearchCase, SearchDir, StartPosition);
|
||||
}
|
||||
|
||||
int32 ULPStringCompat::FindSubstring(const FString& SearchIn, const FString& Substring, bool bUseCase, bool bSearchFromEnd, int32 StartPosition)
|
||||
{
|
||||
// Meme corps que UKismetStringLibrary::FindSubstring, avec FindUE4.
|
||||
const ESearchCase::Type Case = bUseCase ? ESearchCase::CaseSensitive : ESearchCase::IgnoreCase;
|
||||
const ESearchDir::Type Dir = bSearchFromEnd ? ESearchDir::FromEnd : ESearchDir::FromStart;
|
||||
return FindUE4(SearchIn, Substring, Case, Dir, StartPosition);
|
||||
}
|
||||
|
||||
bool ULPStringCompat::Contains(const FString& SearchIn, const FString& Substring, bool bUseCase, bool bSearchFromEnd)
|
||||
{
|
||||
return FindSubstring(SearchIn, Substring, bUseCase, bSearchFromEnd) != -1;
|
||||
}
|
||||
|
||||
bool ULPStringCompat::Split(const FString& SourceString, const FString& InStr, FString& LeftS, FString& RightS, ESearchCase::Type SearchCase, ESearchDir::Type SearchDir)
|
||||
{
|
||||
// Meme corps que FString::Split (4.27 et 5.5), avec FindUE4.
|
||||
const int32 InPos = FindUE4(SourceString, InStr, SearchCase, SearchDir);
|
||||
if (InPos < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
LeftS = SourceString.Left(InPos);
|
||||
RightS = SourceString.Mid(InPos + InStr.Len());
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,14 @@
|
|||
// 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.
|
||||
//
|
||||
// Correctif CX-B12 (lot L7i) : FString::Find avec une sous-chaine VIDE, recherche insensible a la
|
||||
// casse depuis le debut (mode par defaut des noeuds Contains, Find Substring et Split) :
|
||||
// - UE 4.27 : FCString::Stristr cherche le caractere nul final et ne le trouve jamais -> INDEX_NONE ;
|
||||
// - UE 5.5 : TCString::Strnistr renvoie le debut de la chaine -> 0.
|
||||
// "Contains(x, "")" est donc faux en 4.27 et vrai en 5.5. Les autres modes (sensible a la casse,
|
||||
// depuis la fin) donnent le meme resultat dans les deux moteurs. Contains, FindSubstring et Split
|
||||
// ci-dessous reprennent les parametres des fonctions de UKismetStringLibrary et appliquent la regle 4.27.
|
||||
|
||||
#pragma once
|
||||
|
||||
|
|
@ -29,4 +37,19 @@ 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);
|
||||
|
||||
/** FindSubstring avec le comportement de UE 4.27 pour une sous-chaine vide (voir en-tete). */
|
||||
UFUNCTION(BlueprintPure, Category = "Utilities|String", meta = (DisplayName = "Find Substring (UE4)"))
|
||||
static int32 FindSubstring(const FString& SearchIn, const FString& Substring, bool bUseCase = false, bool bSearchFromEnd = false, int32 StartPosition = -1);
|
||||
|
||||
/** Contains avec le comportement de UE 4.27 pour une sous-chaine vide (voir en-tete). */
|
||||
UFUNCTION(BlueprintPure, Category = "Utilities|String", meta = (DisplayName = "Contains (UE4)"))
|
||||
static bool Contains(const FString& SearchIn, const FString& Substring, bool bUseCase = false, bool bSearchFromEnd = false);
|
||||
|
||||
/** Split avec le comportement de UE 4.27 pour un separateur vide (voir en-tete). */
|
||||
UFUNCTION(BlueprintPure, Category = "Utilities|String", meta = (DisplayName = "Split (UE4)"))
|
||||
static bool Split(const FString& SourceString, const FString& InStr, FString& LeftS, FString& RightS, ESearchCase::Type SearchCase = ESearchCase::IgnoreCase, ESearchDir::Type SearchDir = ESearchDir::FromStart);
|
||||
|
||||
/** FString::Find de UE 4.27 : seul ecart avec UE 5.5, la sous-chaine vide en mode insensible a la casse depuis le debut. */
|
||||
static int32 FindUE4(const FString& Str, const FString& SubStr, ESearchCase::Type SearchCase, ESearchDir::Type SearchDir, int32 StartPosition = INDEX_NONE);
|
||||
};
|
||||
|
|
|
|||
91
Source/LifePlayEditorTools/LPDumpCDOCommandlet.cpp
Normal file
91
Source/LifePlayEditorTools/LPDumpCDOCommandlet.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// Portage UE 5.5 - voir LPDumpCDOCommandlet.h
|
||||
|
||||
#include "LPDumpCDOCommandlet.h"
|
||||
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogLPDumpCDO, Log, All);
|
||||
|
||||
ULPDumpCDOCommandlet::ULPDumpCDOCommandlet()
|
||||
{
|
||||
IsClient = false;
|
||||
IsServer = false;
|
||||
IsEditor = true;
|
||||
LogToConsole = true;
|
||||
}
|
||||
|
||||
int32 ULPDumpCDOCommandlet::Main(const FString& Params)
|
||||
{
|
||||
TArray<FString> Tokens, Switches;
|
||||
TMap<FString, FString> ParamsMap;
|
||||
ParseCommandLine(*Params, Tokens, Switches, ParamsMap);
|
||||
|
||||
const FString ClassPath = ParamsMap.FindRef(TEXT("Class"));
|
||||
const FString Out = ParamsMap.FindRef(TEXT("Out"));
|
||||
TArray<FString> Wanted;
|
||||
ParamsMap.FindRef(TEXT("Props")).ParseIntoArray(Wanted, TEXT(","), true);
|
||||
if (ClassPath.IsEmpty() || Out.IsEmpty() || Wanted.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogLPDumpCDO, Error, TEXT("Usage : -Class=/Game/X.X_C -Out=<fichier.txt> -Props=Nom1,Nom2"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
UClass* Class = LoadObject<UClass>(nullptr, *ClassPath);
|
||||
if (!Class)
|
||||
{
|
||||
UE_LOG(LogLPDumpCDO, Error, TEXT("Classe introuvable : %s"), *ClassPath);
|
||||
return 1;
|
||||
}
|
||||
const UObject* CDO = Class->GetDefaultObject();
|
||||
|
||||
TArray<FString> Lines;
|
||||
Lines.Add(FString::Printf(TEXT("##### %s"), *ClassPath));
|
||||
for (const FString& Name : Wanted)
|
||||
{
|
||||
const FProperty* Prop = Class->FindPropertyByName(FName(*Name));
|
||||
if (!Prop)
|
||||
{
|
||||
Lines.Add(FString::Printf(TEXT("%s : introuvable"), *Name));
|
||||
continue;
|
||||
}
|
||||
const void* Value = Prop->ContainerPtrToValuePtr<void>(CDO);
|
||||
if (const FMapProperty* MapProp = CastField<FMapProperty>(Prop))
|
||||
{
|
||||
FScriptMapHelper Helper(MapProp, Value);
|
||||
Lines.Add(FString::Printf(TEXT("%s [map] : %d entree(s)"), *Name, Helper.Num()));
|
||||
for (FScriptMapHelper::FIterator It(Helper); It; ++It)
|
||||
{
|
||||
FString Key, Val;
|
||||
MapProp->KeyProp->ExportTextItem_Direct(Key, Helper.GetKeyPtr(It), nullptr, nullptr, PPF_None);
|
||||
MapProp->ValueProp->ExportTextItem_Direct(Val, Helper.GetValuePtr(It), nullptr, nullptr, PPF_None);
|
||||
Lines.Add(FString::Printf(TEXT(" %s = %s"), *Key, *Val));
|
||||
}
|
||||
}
|
||||
else if (const FArrayProperty* ArrayProp = CastField<FArrayProperty>(Prop))
|
||||
{
|
||||
FScriptArrayHelper Helper(ArrayProp, Value);
|
||||
Lines.Add(FString::Printf(TEXT("%s [tableau] : %d element(s)"), *Name, Helper.Num()));
|
||||
for (int32 i = 0; i < Helper.Num(); ++i)
|
||||
{
|
||||
FString Text;
|
||||
ArrayProp->Inner->ExportTextItem_Direct(Text, Helper.GetRawPtr(i), nullptr, nullptr, PPF_None);
|
||||
Lines.Add(FString::Printf(TEXT(" [%d] = %s"), i, *Text));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FString Text;
|
||||
Prop->ExportTextItem_Direct(Text, Value, nullptr, nullptr, PPF_None);
|
||||
Lines.Add(FString::Printf(TEXT("%s = %s"), *Name, *Text));
|
||||
}
|
||||
}
|
||||
|
||||
if (!FFileHelper::SaveStringArrayToFile(Lines, *Out, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
|
||||
{
|
||||
UE_LOG(LogLPDumpCDO, Error, TEXT("Ecriture impossible : %s"), *Out);
|
||||
return 1;
|
||||
}
|
||||
UE_LOG(LogLPDumpCDO, Warning, TEXT("=== %s : %d ligne(s) -> %s ==="), *ClassPath, Lines.Num(), *Out);
|
||||
return 0;
|
||||
}
|
||||
23
Source/LifePlayEditorTools/LPDumpCDOCommandlet.h
Normal file
23
Source/LifePlayEditorTools/LPDumpCDOCommandlet.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Portage UE 5.5 - lot L7i (diagnostic getBeautyValue).
|
||||
//
|
||||
// Ecrit en texte des proprietes de l'objet par defaut (CDO) d'une classe Blueprint : un element
|
||||
// de tableau ou une entree de map par ligne. Lecture seule.
|
||||
//
|
||||
// Usage :
|
||||
// UnrealEditor-Cmd.exe <projet> -run=LPDumpCDO -Class=/Game/BPs/GI.GI_C -Out=<fichier.txt> -Props=Nom1,Nom2
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Commandlets/Commandlet.h"
|
||||
#include "LPDumpCDOCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class ULPDumpCDOCommandlet : public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ULPDumpCDOCommandlet();
|
||||
virtual int32 Main(const FString& Params) override;
|
||||
};
|
||||
323
Source/LifePlayEditorTools/LPFixStringSearchCommandlet.cpp
Normal file
323
Source/LifePlayEditorTools/LPFixStringSearchCommandlet.cpp
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
// Portage UE 5.5 - voir LPFixStringSearchCommandlet.h
|
||||
|
||||
#include "LPFixStringSearchCommandlet.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 "Kismet/KismetStringLibrary.h"
|
||||
#include "LPStringCompat.h"
|
||||
#include "Misc/PackageName.h"
|
||||
#include "UObject/Package.h"
|
||||
#include "UObject/SavePackage.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogLPFixStringSearch, Log, All);
|
||||
|
||||
namespace
|
||||
{
|
||||
/** Fonctions rebranchees : meme nom, memes broches dans UKismetStringLibrary et ULPStringCompat. */
|
||||
const FName FunctionNames[] = { FName(TEXT("Contains")), FName(TEXT("FindSubstring")), FName(TEXT("Split")) };
|
||||
|
||||
/** 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());
|
||||
}
|
||||
|
||||
/** Fonction de remplacement si le noeud appelle une des fonctions visees de UKismetStringLibrary, sinon nullptr. */
|
||||
UFunction* ReplacementFor(const UK2Node_CallFunction* Node)
|
||||
{
|
||||
if (!Node)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const UClass* Owner = Node->FunctionReference.GetMemberParentClass();
|
||||
if (!Owner || !Owner->IsChildOf(UKismetStringLibrary::StaticClass()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const FName Name = Node->FunctionReference.GetMemberName();
|
||||
for (const FName& Wanted : FunctionNames)
|
||||
{
|
||||
if (Name == Wanted)
|
||||
{
|
||||
return ULPStringCompat::StaticClass()->FindFunctionByName(Wanted);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool HasExecPins(const UEdGraphNode* Node)
|
||||
{
|
||||
for (const UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ULPFixStringSearchCommandlet::ULPFixStringSearchCommandlet()
|
||||
{
|
||||
IsClient = false;
|
||||
IsServer = false;
|
||||
IsEditor = true;
|
||||
LogToConsole = true;
|
||||
}
|
||||
|
||||
int32 ULPFixStringSearchCommandlet::Main(const FString& Params)
|
||||
{
|
||||
const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase);
|
||||
|
||||
for (const FName& Wanted : FunctionNames)
|
||||
{
|
||||
if (!ULPStringCompat::StaticClass()->FindFunctionByName(Wanted))
|
||||
{
|
||||
UE_LOG(LogLPFixStringSearch, Error, TEXT("ULPStringCompat::%s introuvable."), *Wanted.ToString());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
TMap<FName, int32> PerFunction;
|
||||
|
||||
UE_LOG(LogLPFixStringSearch, Warning, TEXT("=== Contains / FindSubstring / Split -> LPStringCompat (UE4)%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.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;
|
||||
}
|
||||
// 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(LogLPFixStringSearch, 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 && Pin->PinName != UEdGraphSchema_K2::PN_Self)
|
||||
{
|
||||
Before.Add(Pin->PinName, PinState(Pin));
|
||||
}
|
||||
}
|
||||
const UEdGraphPin* OldSelf = OldNode->FindPin(UEdGraphSchema_K2::PN_Self, EGPD_Input);
|
||||
|
||||
if (HasExecPins(OldNode) || (OldSelf && OldSelf->LinkedTo.Num() > 0))
|
||||
{
|
||||
++Unhandled;
|
||||
++FailedHere;
|
||||
UE_LOG(LogLPFixStringSearch, Error, TEXT(" cas non pris en charge (%s) : noeud laisse en l'etat."),
|
||||
HasExecPins(OldNode) ? TEXT("noeud impur") : 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 || OldPin->PinName == UEdGraphSchema_K2::PN_Self)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UEdGraphPin* NewPin = NewNode->FindPin(OldPin->PinName, OldPin->Direction);
|
||||
if (!NewPin)
|
||||
{
|
||||
bOk = false;
|
||||
UE_LOG(LogLPFixStringSearch, 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(LogLPFixStringSearch, 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 || NewPin->PinName == UEdGraphSchema_K2::PN_Self)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FString* Expected = Before.Find(NewPin->PinName);
|
||||
if (!Expected || *Expected != PinState(NewPin))
|
||||
{
|
||||
bOk = false;
|
||||
UE_LOG(LogLPFixStringSearch, 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(LogLPFixStringSearch, 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(LogLPFixStringSearch, Error, TEXT(" ecart : Blueprint non sauvegarde."));
|
||||
continue;
|
||||
}
|
||||
|
||||
OldNode->DestroyNode();
|
||||
++NodesFixed;
|
||||
++FixedHere;
|
||||
}
|
||||
}
|
||||
|
||||
if (FailedHere > 0)
|
||||
{
|
||||
UE_LOG(LogLPFixStringSearch, 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(LogLPFixStringSearch, 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(LogLPFixStringSearch, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere);
|
||||
}
|
||||
else
|
||||
{
|
||||
++SaveFailures;
|
||||
UE_LOG(LogLPFixStringSearch, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
for (const TPair<FName, int32>& It : PerFunction)
|
||||
{
|
||||
UE_LOG(LogLPFixStringSearch, Warning, TEXT(" %s : %d noeud(s)"), *It.Key.ToString(), It.Value);
|
||||
}
|
||||
UE_LOG(LogLPFixStringSearch, 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;
|
||||
}
|
||||
28
Source/LifePlayEditorTools/LPFixStringSearchCommandlet.h
Normal file
28
Source/LifePlayEditorTools/LPFixStringSearchCommandlet.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Portage UE 5.5 - lot L7i (CX-B12). Voir Source/LifePlay/LPStringCompat.h.
|
||||
//
|
||||
// Rebranche chaque appel a UKismetStringLibrary::Contains, FindSubstring et Split sur la fonction
|
||||
// homonyme de ULPStringCompat (memes broches, comportement 4.27 pour une sous-chaine vide), au meme
|
||||
// emplacement. Liaisons et valeurs par defaut sont reportees broche par broche, par nom.
|
||||
//
|
||||
// Controle integre (repris de LPFixNavRandomPoint, CX-B11) : l'etat de chaque broche (liaisons,
|
||||
// valeur par defaut) est compare avant et apres. Tout ecart, broche sans equivalent, cible self
|
||||
// reliee ou noeud impur : le noeud est laisse en l'etat et son Blueprint n'est pas sauvegarde.
|
||||
//
|
||||
// Usage :
|
||||
// UnrealEditor-Cmd.exe <projet> -run=LPFixStringSearch [-DryRun]
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Commandlets/Commandlet.h"
|
||||
#include "LPFixStringSearchCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class ULPFixStringSearchCommandlet : public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ULPFixStringSearchCommandlet();
|
||||
virtual int32 Main(const FString& Params) override;
|
||||
};
|
||||
164
Source/LifePlayEditorTools/LPTestStringCompatCommandlet.cpp
Normal file
164
Source/LifePlayEditorTools/LPTestStringCompatCommandlet.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// Portage UE 5.5 - voir LPTestStringCompatCommandlet.h
|
||||
|
||||
#include "LPTestStringCompatCommandlet.h"
|
||||
|
||||
#include "Kismet/KismetStringLibrary.h"
|
||||
#include "LPStringCompat.h"
|
||||
#include <wchar.h>
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogLPTestStringCompat, Log, All);
|
||||
|
||||
namespace
|
||||
{
|
||||
/** TCString<TCHAR>::Stristr, UE 4.27 (CString.h l. 622-650), transcrit a l'identique. */
|
||||
const TCHAR* Stristr427(const TCHAR* Str, const TCHAR* Find)
|
||||
{
|
||||
if (Find == nullptr || Str == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
TCHAR FindInitial = FChar::ToUpper(*Find);
|
||||
int32 Length = FCString::Strlen(Find++) - 1;
|
||||
TCHAR StrChar = *Str++;
|
||||
while (StrChar)
|
||||
{
|
||||
StrChar = FChar::ToUpper(StrChar);
|
||||
if (StrChar == FindInitial && !FCString::Strnicmp(Str, Find, Length))
|
||||
{
|
||||
return Str - 1;
|
||||
}
|
||||
StrChar = *Str++;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** FString::Find, UE 4.27 (String.cpp l. 180-227), transcrit a l'identique. */
|
||||
int32 Find427(const FString& This, const TCHAR* SubStr, ESearchCase::Type SearchCase, ESearchDir::Type SearchDir, int32 StartPosition)
|
||||
{
|
||||
if (SubStr == nullptr)
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
if (SearchDir == ESearchDir::FromStart)
|
||||
{
|
||||
const TCHAR* Start = *This;
|
||||
if (StartPosition != INDEX_NONE && This.Len() > 0)
|
||||
{
|
||||
Start += FMath::Clamp(StartPosition, 0, This.Len() - 1);
|
||||
}
|
||||
// FCString::Strstr 4.27 sous Windows : FWindowsPlatformString::Strstr = _tcsstr (wcsstr).
|
||||
const TCHAR* Tmp = SearchCase == ESearchCase::IgnoreCase
|
||||
? Stristr427(Start, SubStr)
|
||||
: (const TCHAR*)wcsstr((const wchar_t*)Start, (const wchar_t*)SubStr);
|
||||
return Tmp ? UE_PTRDIFF_TO_INT32(Tmp - *This) : INDEX_NONE;
|
||||
}
|
||||
if (SearchCase == ESearchCase::IgnoreCase)
|
||||
{
|
||||
return Find427(This.ToUpper(), *FString(SubStr).ToUpper(), ESearchCase::CaseSensitive, SearchDir, StartPosition);
|
||||
}
|
||||
const int32 SearchStringLength = FMath::Max(1, FCString::Strlen(SubStr));
|
||||
if (StartPosition == INDEX_NONE || StartPosition >= This.Len())
|
||||
{
|
||||
StartPosition = This.Len();
|
||||
}
|
||||
// En 4.27, (*this)[i+j] pouvait lire le '\0' final sans controle : on lit le tampon brut.
|
||||
const TCHAR* Data = *This;
|
||||
for (int32 i = StartPosition - SearchStringLength; i >= 0; i--)
|
||||
{
|
||||
int32 j;
|
||||
for (j = 0; SubStr[j]; j++)
|
||||
{
|
||||
if (Data[i + j] != SubStr[j])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!SubStr[j])
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INDEX_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
ULPTestStringCompatCommandlet::ULPTestStringCompatCommandlet()
|
||||
{
|
||||
IsClient = false;
|
||||
IsServer = false;
|
||||
IsEditor = true;
|
||||
LogToConsole = true;
|
||||
}
|
||||
|
||||
int32 ULPTestStringCompatCommandlet::Main(const FString& Params)
|
||||
{
|
||||
const TArray<FString> Strings = { TEXT(""), TEXT("a"), TEXT("A"), TEXT("abc"), TEXT("ABC"), TEXT("aXbXc"), TEXT("round, oval"), TEXT(" "), TEXT("teen"), TEXT("Daz") };
|
||||
const TArray<FString> Subs = { TEXT(""), TEXT("a"), TEXT("A"), TEXT("b"), TEXT("X"), TEXT("bc"), TEXT("abc"), TEXT("abcd"), TEXT(", "), TEXT(" "), TEXT("oval"), TEXT("TEEN"), TEXT("daz") };
|
||||
const TArray<int32> Starts = { -1, 0, 1, 2, 5, 50 };
|
||||
|
||||
int32 Cases = 0, CompatMismatch = 0, EngineMismatch = 0;
|
||||
for (const FString& S : Strings)
|
||||
{
|
||||
for (const FString& Sub : Subs)
|
||||
{
|
||||
for (int32 CaseIdx = 0; CaseIdx < 2; ++CaseIdx)
|
||||
{
|
||||
for (int32 DirIdx = 0; DirIdx < 2; ++DirIdx)
|
||||
{
|
||||
for (const int32 Start : Starts)
|
||||
{
|
||||
const ESearchCase::Type Case = CaseIdx ? ESearchCase::CaseSensitive : ESearchCase::IgnoreCase;
|
||||
const ESearchDir::Type Dir = DirIdx ? ESearchDir::FromEnd : ESearchDir::FromStart;
|
||||
++Cases;
|
||||
|
||||
const int32 Ref = Find427(S, *Sub, Case, Dir, Start);
|
||||
const int32 Compat = ULPStringCompat::FindUE4(S, Sub, Case, Dir, Start);
|
||||
const int32 Engine = S.Find(Sub, Case, Dir, Start);
|
||||
|
||||
if (Compat != Ref)
|
||||
{
|
||||
++CompatMismatch;
|
||||
UE_LOG(LogLPTestStringCompat, Error, TEXT("COMPAT Find(\"%s\", \"%s\", %s, %s, %d) : 4.27=%d compat=%d"),
|
||||
*S, *Sub, CaseIdx ? TEXT("Case") : TEXT("Ignore"), DirIdx ? TEXT("FromEnd") : TEXT("FromStart"), Start, Ref, Compat);
|
||||
}
|
||||
if (Engine != Ref)
|
||||
{
|
||||
++EngineMismatch;
|
||||
UE_LOG(LogLPTestStringCompat, Display, TEXT("moteur Find(\"%s\", \"%s\", %s, %s, %d) : 4.27=%d 5.5=%d"),
|
||||
*S, *Sub, CaseIdx ? TEXT("Case") : TEXT("Ignore"), DirIdx ? TEXT("FromEnd") : TEXT("FromStart"), Start, Ref, Engine);
|
||||
}
|
||||
|
||||
// Fonctions Blueprint : uniquement avec la position de depart par defaut.
|
||||
if (Start == -1)
|
||||
{
|
||||
const bool bUseCase = CaseIdx == 1, bFromEnd = DirIdx == 1;
|
||||
if (ULPStringCompat::Contains(S, Sub, bUseCase, bFromEnd) != (Ref != INDEX_NONE)
|
||||
|| ULPStringCompat::FindSubstring(S, Sub, bUseCase, bFromEnd) != Ref)
|
||||
{
|
||||
++CompatMismatch;
|
||||
UE_LOG(LogLPTestStringCompat, Error, TEXT("COMPAT Contains/FindSubstring(\"%s\", \"%s\") ecart"), *S, *Sub);
|
||||
}
|
||||
FString L1, R1, L2, R2;
|
||||
const bool B1 = ULPStringCompat::Split(S, Sub, L1, R1, Case, Dir);
|
||||
const bool B2 = Ref >= 0;
|
||||
if (B1 != B2 || (B2 && (L1 != S.Left(Ref) || R1 != S.Mid(Ref + Sub.Len()))))
|
||||
{
|
||||
++CompatMismatch;
|
||||
UE_LOG(LogLPTestStringCompat, Error, TEXT("COMPAT Split(\"%s\", \"%s\") ecart"), *S, *Sub);
|
||||
}
|
||||
// Cas Blueprint par defaut, moteur 5.5 contre 4.27 : documente l'ecart vise.
|
||||
if (!bUseCase && !bFromEnd && UKismetStringLibrary::Contains(S, Sub) != (Ref != INDEX_NONE))
|
||||
{
|
||||
UE_LOG(LogLPTestStringCompat, Display, TEXT("moteur Contains(\"%s\", \"%s\") : 4.27=%d 5.5=%d"),
|
||||
*S, *Sub, Ref != INDEX_NONE, UKismetStringLibrary::Contains(S, Sub));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogLPTestStringCompat, Warning, TEXT("=== %d cas : compat != 4.27 : %d ; moteur 5.5 != 4.27 : %d ==="), Cases, CompatMismatch, EngineMismatch);
|
||||
return CompatMismatch > 0 ? 1 : 0;
|
||||
}
|
||||
25
Source/LifePlayEditorTools/LPTestStringCompatCommandlet.h
Normal file
25
Source/LifePlayEditorTools/LPTestStringCompatCommandlet.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Portage UE 5.5 - lot L7i (CX-B12). Test de ULPStringCompat::FindUE4 / Contains / FindSubstring / Split.
|
||||
//
|
||||
// Compare, sur une grille de cas limites (chaines vides, casse, sens, position de depart) :
|
||||
// - une transcription litterale de FString::Find de UE 4.27 (String.cpp 4.27 l. 180-227, avec
|
||||
// TCString::Stristr 4.27 et wcsstr pour FCString::Strstr sous Windows) ;
|
||||
// - ULPStringCompat (doit lui etre identique) ;
|
||||
// - le moteur 5.5 (les ecarts sont listes, ils justifient le correctif).
|
||||
//
|
||||
// Usage : UnrealEditor-Cmd.exe <projet> -run=LPTestStringCompat
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Commandlets/Commandlet.h"
|
||||
#include "LPTestStringCompatCommandlet.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class ULPTestStringCompatCommandlet : public UCommandlet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ULPTestStringCompatCommandlet();
|
||||
virtual int32 Main(const FString& Params) override;
|
||||
};
|
||||
|
|
@ -9,7 +9,7 @@ def parse(p):
|
|||
if key: objs[key]['lines'].append(line)
|
||||
return objs
|
||||
# Profils de remplacement : selection des anciens et nouveaux noeuds, correspondance des broches.
|
||||
# Usage : DiffRemplacementNoeuds.py avant.txt apres.txt [L7a|L7b] (L7a par defaut)
|
||||
# Usage : DiffRemplacementNoeuds.py avant.txt apres.txt [L7a|L7b|CX-B12] (L7a par defaut)
|
||||
PROFILS={
|
||||
'L7a':{'old':('K2Node_VariableGet',lambda l:'Engine.SkinnedMeshComponent\'",MemberName="SkeletalMesh"' in l),
|
||||
'new':('K2Node_CallFunction',lambda l:'MemberName="GetSkeletalMesh"' in l and 'LPMeshCompat' in l),
|
||||
|
|
@ -17,6 +17,9 @@ PROFILS={
|
|||
'L7b':{'old':('K2Node_CallFunction',lambda l:'MemberName="K2_GetRandomPointInNavigableRadius"' in l),
|
||||
'new':('K2Node_CallFunction',lambda l:'MemberName="GetRandomLocationInNavigableRadius"' in l and 'LPNavigationHelpers' in l),
|
||||
'pins':[(x,x) for x in ('WorldContextObject','Origin','RandomLocation','Radius','NavData','FilterClass','ReturnValue')]},
|
||||
'CX-B12':{'old':('K2Node_CallFunction',lambda l:'KismetStringLibrary' in l and re.search(r'MemberName="(Contains|FindSubstring|Split)"',l) is not None),
|
||||
'new':('K2Node_CallFunction',lambda l:'LPStringCompat' in l and re.search(r'MemberName="(Contains|FindSubstring|Split)"',l) is not None),
|
||||
'pins':[(x,x) for x in ('SearchIn','Substring','bUseCase','bSearchFromEnd','StartPosition','SourceString','InStr','LeftS','RightS','SearchCase','SearchDir','ReturnValue')]},
|
||||
}
|
||||
PROFIL=PROFILS[sys.argv[3] if len(sys.argv)>3 else 'L7a']
|
||||
A=parse(sys.argv[1]); B=parse(sys.argv[2])
|
||||
|
|
@ -107,7 +110,8 @@ for k,v in old.items():
|
|||
if p2==k[0] and gg==g: x=x.replace(o+'.',nn+'.') if x.split('.')[0]==o else x
|
||||
out.append(x)
|
||||
return sorted(out)
|
||||
same=all(rn(pinlinks(v,op))==pinlinks(n,np_) for op,np_ in PROFIL['pins'])
|
||||
# Broche absente des deux cotes (profil multi-fonctions : Split n'a pas SearchIn...) : egalite.
|
||||
same=all((pinlinks(v,op) is None and pinlinks(n,np_) is None) or rn(pinlinks(v,op))==pinlinks(n,np_) for op,np_ in PROFIL['pins'])
|
||||
# Profil a broches homonymes : les valeurs par defaut doivent aussi etre reportees.
|
||||
if same and all(op==np_ for op,np_ in PROFIL['pins']): same=all(pinhead(v,op)==pinhead(n,op) for op,_ in PROFIL['pins'])
|
||||
if same: ok+=1
|
||||
|
|
|
|||
Loading…
Reference in a new issue