// Portage UE 5.5 - voir LPCheckStreetMapCommandlet.h #include "LPCheckStreetMapCommandlet.h" #include "AssetRegistry/AssetRegistryModule.h" #include "HAL/FileManager.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "StreetMap.h" #include "UObject/UObjectGlobals.h" DEFINE_LOG_CATEGORY_STATIC(LogLPCheckStreetMap, Log, All); namespace { /** Ce qu'on a trouve dans une ville. */ struct FCityReport { FString Name; int32 Roads = 0; int32 Nodes = 0; int32 Buildings = 0; int32 Waters = 0; int32 Parks = 0; int32 NamedBuildings = 0; // batiments avec un nom non vide int32 WithAmenity = 0; // batiments avec un Amenity non vide int32 DistinctAmenities = 0; bool bLoaded = false; }; } ULPCheckStreetMapCommandlet::ULPCheckStreetMapCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPCheckStreetMapCommandlet::Main(const FString& Params) { TArray Tokens; TArray Switches; TMap ParamsMap; ParseCommandLine(*Params, Tokens, Switches, ParamsMap); const bool bVerbose = Switches.Contains(TEXT("Verbose")); const bool bDumpAmenities = Switches.Contains(TEXT("DumpAmenities")); const FString CsvPath = ParamsMap.FindRef(TEXT("CSV")); // Lot L7h : limite la verification a un dossier (ex. copie temporaire des assets 4.27). const FString PathFilter = ParamsMap.FindRef(TEXT("Path")); UE_LOG(LogLPCheckStreetMap, Warning, TEXT("=== Verification des assets UStreetMap ===")); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray Assets; AssetRegistryModule.Get().GetAssetsByClass(UStreetMap::StaticClass()->GetClassPathName(), Assets, true); if (!PathFilter.IsEmpty()) { Assets.RemoveAll([&PathFilter](const FAssetData& A) { return !A.PackageName.ToString().StartsWith(PathFilter); }); } // Ordre stable : indispensable pour diffing entre deux executions. Assets.Sort([](const FAssetData& A, const FAssetData& B) { return A.AssetName.LexicalLess(B.AssetName); }); UE_LOG(LogLPCheckStreetMap, Warning, TEXT("%d asset(s) UStreetMap trouve(s)."), Assets.Num()); TArray Reports; Reports.Reserve(Assets.Num()); int32 LoadFailures = 0; int32 EmptyCities = 0; for (const FAssetData& AssetData : Assets) { FCityReport Report; Report.Name = AssetData.AssetName.ToString(); UStreetMap* StreetMap = Cast(AssetData.GetAsset()); if (StreetMap == nullptr) { ++LoadFailures; UE_LOG(LogLPCheckStreetMap, Error, TEXT(" ECHEC de chargement : %s"), *Report.Name); Reports.Add(Report); continue; } Report.bLoaded = true; Report.Roads = StreetMap->GetRoads().Num(); Report.Nodes = StreetMap->GetNodes().Num(); Report.Buildings = StreetMap->GetBuildings().Num(); Report.Waters = StreetMap->GetWaters().Num(); Report.Parks = StreetMap->GetParks().Num(); // Amenity est le mecanisme par lequel le jeu identifie bars, salles de sport et // commerces : c'est la donnee dont la perte serait la plus grave. TSet Amenities; for (const FStreetMapBuilding& Building : StreetMap->GetBuildings()) { if (!Building.BuildingName.IsEmpty()) { ++Report.NamedBuildings; } if (!Building.Amenity.IsEmpty()) { ++Report.WithAmenity; Amenities.Add(Building.Amenity); } } Report.DistinctAmenities = Amenities.Num(); // Les comptes seuls ne prouvent pas que la donnee est la bonne : on releve les // valeurs pour pouvoir les croiser avec les types de lieux du jeu (*.lpworld). if (bDumpAmenities && Amenities.Num() > 0) { TArray Sorted = Amenities.Array(); Sorted.Sort(); UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" --- amenities de %s (%d) : %s"), *Report.Name, Sorted.Num(), *FString::Join(Sorted, TEXT(", "))); } if (Report.Roads == 0 && Report.Buildings == 0) { ++EmptyCities; } if (bVerbose) { UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" %-24s routes=%-7d noeuds=%-7d batiments=%-7d eaux=%-4d parcs=%-4d nommes=%-6d amenity=%-6d (%d types)"), *Report.Name, Report.Roads, Report.Nodes, Report.Buildings, Report.Waters, Report.Parks, Report.NamedBuildings, Report.WithAmenity, Report.DistinctAmenities); } Reports.Add(Report); // Sans cela, charger les 280 villes fait exploser la memoire (CentralLondon pese // 33 Mo de donnees a lui seul). CollectGarbage(RF_NoFlags); } // --- Synthese int64 TotalRoads = 0, TotalBuildings = 0, TotalWaters = 0, TotalParks = 0, TotalAmenity = 0; for (const FCityReport& R : Reports) { TotalRoads += R.Roads; TotalBuildings += R.Buildings; TotalWaters += R.Waters; TotalParks += R.Parks; TotalAmenity += R.WithAmenity; } UE_LOG(LogLPCheckStreetMap, Warning, TEXT("=== Synthese ===")); UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" villes chargees : %d / %d"), Assets.Num() - LoadFailures, Assets.Num()); UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" echecs de chargement : %d"), LoadFailures); UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" villes sans donnees : %d"), EmptyCities); UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" routes : %lld"), TotalRoads); UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" batiments : %lld"), TotalBuildings); UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" batiments avec amenity : %lld"), TotalAmenity); UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" plans d'eau : %lld"), TotalWaters); UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" parcs : %lld"), TotalParks); if (!CsvPath.IsEmpty()) { FString Csv = TEXT("Ville;Charge;Routes;Noeuds;Batiments;Eaux;Parcs;BatimentsNommes;AvecAmenity;TypesAmenity\n"); for (const FCityReport& R : Reports) { Csv += FString::Printf(TEXT("%s;%d;%d;%d;%d;%d;%d;%d;%d;%d\n"), *R.Name, R.bLoaded ? 1 : 0, R.Roads, R.Nodes, R.Buildings, R.Waters, R.Parks, R.NamedBuildings, R.WithAmenity, R.DistinctAmenities); } if (FFileHelper::SaveStringToFile(Csv, *CsvPath)) { UE_LOG(LogLPCheckStreetMap, Warning, TEXT(" CSV ecrit : %s"), *CsvPath); } else { UE_LOG(LogLPCheckStreetMap, Error, TEXT(" ECHEC d'ecriture du CSV : %s"), *CsvPath); } } // Code de retour non nul si une ville n'a pas pu etre chargee : exploitable en CI. return LoadFailures > 0 ? 1 : 0; }