// Copyright Epic Games, Inc. All Rights Reserved. #include "PackageStoreOptimizer.h" #include "ProfilingDebugging/CountersTrace.h" #include "Serialization/BufferWriter.h" #include "Serialization/LargeMemoryWriter.h" #include "Serialization/MemoryReader.h" #include "UObject/NameBatchSerialization.h" #include "Containers/Map.h" #include "UObject/UObjectHash.h" #include "Interfaces/ITargetPlatform.h" #include "UObject/Object.h" #include "Serialization/MemoryReader.h" #include "UObject/Package.h" #include "Misc/PackageName.h" #include "Misc/SecureHash.h" #include "Misc/StringBuilder.h" #include "Serialization/LargeMemoryReader.h" #include "VerseVM/VVMVerse.h" #if WITH_VERSE_VM || defined(__INTELLISENSE__) #include "VerseVM/VVMContext.h" #include "VerseVM/VVMCppClassInfo.h" #include "VerseVM/VVMGlobalProgram.h" #include "VerseVM/VVMUniqueString.h" #endif DEFINE_LOG_CATEGORY_STATIC(LogPackageStoreOptimizer, Log, All); // modified copy from SavePackage EObjectMark GetExcludedObjectMarksForTargetPlatform(const ITargetPlatform* TargetPlatform) { EObjectMark Marks = OBJECTMARK_NotForTargetPlatform; if (!TargetPlatform->AllowsEditorObjects()) { Marks = (EObjectMark)(Marks | OBJECTMARK_EditorOnly); } if (TargetPlatform->IsServerOnly()) { Marks = (EObjectMark)(Marks | OBJECTMARK_NotForServer); } if (TargetPlatform->IsClientOnly()) { Marks = (EObjectMark)(Marks | OBJECTMARK_NotForClient); } return Marks; } // modified copy from SavePackage EObjectMark GetExcludedObjectMarksForObject(const UObject* Object, const ITargetPlatform* TargetPlatform) { EObjectMark Marks = OBJECTMARK_NOMARKS; if (!Object->NeedsLoadForClient()) { Marks = (EObjectMark)(Marks | OBJECTMARK_NotForClient); } if (!Object->NeedsLoadForServer()) { Marks = (EObjectMark)(Marks | OBJECTMARK_NotForServer); } #if WITH_ENGINE // NotForServer && NotForClient implies EditorOnly const bool bIsEditorOnlyObject = (Marks & OBJECTMARK_NotForServer) && (Marks & OBJECTMARK_NotForClient); const bool bTargetAllowsEditorObjects = TargetPlatform->AllowsEditorObjects(); // no need to query the target platform if the object is editoronly and the targetplatform doesn't allow editor objects const bool bCheckTargetPlatform = !bIsEditorOnlyObject || bTargetAllowsEditorObjects; if (bCheckTargetPlatform && (!Object->NeedsLoadForTargetPlatform(TargetPlatform) || !TargetPlatform->AllowObject(Object))) { Marks = (EObjectMark)(Marks | OBJECTMARK_NotForTargetPlatform); } #endif if (Object->IsEditorOnly()) { Marks = (EObjectMark)(Marks | OBJECTMARK_EditorOnly); } if ((Marks & OBJECTMARK_NotForClient) && (Marks & OBJECTMARK_NotForServer)) { Marks = (EObjectMark)(Marks | OBJECTMARK_EditorOnly); } return Marks; } void FPackageStoreOptimizer::Initialize() { FindScriptObjects(); } void FPackageStoreOptimizer::Initialize(const FIoBuffer& ScriptObjectsBuffer) { LoadScriptObjectsBuffer(ScriptObjectsBuffer); } FPackageStorePackage* FPackageStoreOptimizer::CreateMissingPackage(const FName& Name) const { FPackageStorePackage* Package = new FPackageStorePackage(); Package->Name = Name; Package->Id = FPackageId::FromName(Name); return Package; } FPackageStorePackage* FPackageStoreOptimizer::CreatePackageFromCookedHeader(const FName& Name, const FIoBuffer& CookedHeaderBuffer) const { FPackageStorePackage* Package = new FPackageStorePackage(); Package->Id = FPackageId::FromName(Name); Package->Name = Name; FCookedHeaderData CookedHeaderData = LoadCookedHeader(CookedHeaderBuffer); if (!CookedHeaderData.Summary.bUnversioned) { FZenPackageVersioningInfo& VersioningInfo = Package->VersioningInfo.Emplace(); VersioningInfo.ZenVersion = EZenPackageVersion::Latest; VersioningInfo.PackageVersion = CookedHeaderData.Summary.GetFileVersionUE(); VersioningInfo.LicenseeVersion = CookedHeaderData.Summary.GetFileVersionLicenseeUE(); VersioningInfo.CustomVersions = CookedHeaderData.Summary.GetCustomVersionContainer(); } Package->PackageFlags = CookedHeaderData.Summary.GetPackageFlags(); Package->CookedHeaderSize = CookedHeaderData.Summary.TotalHeaderSize; for (int32 I = 0; I < CookedHeaderData.Summary.NamesReferencedFromExportDataCount; ++I) { Package->NameMapBuilder.AddName(CookedHeaderData.SummaryNames[I]); } Package->SoftPackageReferences = MoveTemp(CookedHeaderData.SoftPackageReferences); TArray Imports; ProcessImports(CookedHeaderData, Package, Imports); ProcessExports(CookedHeaderData, Package, Imports.GetData()); ProcessPreloadDependencies(CookedHeaderData, Package); ProcessDataResources(CookedHeaderData, Package); CreateExportBundle(Package); FinalizePackageHeader(Package); return Package; } PRAGMA_DISABLE_DEPRECATION_WARNINGS FPackageStoreOptimizer::FCookedHeaderData FPackageStoreOptimizer::LoadCookedHeader(const FIoBuffer& CookedHeaderBuffer) const { FCookedHeaderData CookedHeaderData; TArrayView MemView(CookedHeaderBuffer.Data(), CookedHeaderBuffer.DataSize()); FMemoryReaderView Ar(MemView); FPackageFileSummary& Summary = CookedHeaderData.Summary; { TGuardValue GuardAllowUnversionedContentInEditor(GAllowUnversionedContentInEditor, 1); Ar << Summary; } Ar.SetUseUnversionedPropertySerialization((CookedHeaderData.Summary.GetPackageFlags() & EPackageFlags::PKG_UnversionedProperties) != 0); Ar.SetFilterEditorOnly((CookedHeaderData.Summary.GetPackageFlags() & EPackageFlags::PKG_FilterEditorOnly) != 0); if (Summary.NameCount > 0) { Ar.Seek(Summary.NameOffset); FNameEntrySerialized NameEntry(ENAME_LinkerConstructor); CookedHeaderData.SummaryNames.Reserve(Summary.NameCount); for (int32 I = 0; I < Summary.NameCount; ++I) { Ar << NameEntry; CookedHeaderData.SummaryNames.Add(NameEntry); } } class FNameReaderProxyArchive : public FArchiveProxy { public: using FArchiveProxy::FArchiveProxy; FNameReaderProxyArchive(FArchive& InAr, const TArray& InNameMap) : FArchiveProxy(InAr) , NameMap(InNameMap) { // Replicate the filter editor only state of the InnerArchive as FArchiveProxy will // not intercept it. FArchive::SetFilterEditorOnly(InAr.IsFilterEditorOnly()); } FArchive& operator<<(FName& Name) { int32 NameIndex = 0; int32 Number = 0; InnerArchive << NameIndex << Number; if (!NameMap.IsValidIndex(NameIndex)) { UE_LOG(LogPackageStoreOptimizer, Fatal, TEXT("Bad name index %i/%i"), NameIndex, NameMap.Num()); } const FName& MappedName = NameMap[NameIndex]; Name = FName::CreateFromDisplayId(MappedName.GetDisplayIndex(), Number); return *this; } private: const TArray& NameMap; }; FNameReaderProxyArchive ProxyAr(Ar, CookedHeaderData.SummaryNames); if (Summary.ImportCount > 0) { CookedHeaderData.ObjectImports.Reserve(Summary.ImportCount); ProxyAr.Seek(Summary.ImportOffset); for (int32 I = 0; I < Summary.ImportCount; ++I) { ProxyAr << CookedHeaderData.ObjectImports.AddDefaulted_GetRef(); } } if (Summary.PreloadDependencyCount > 0) { CookedHeaderData.PreloadDependencies.Reserve(Summary.PreloadDependencyCount); ProxyAr.Seek(Summary.PreloadDependencyOffset); for (int32 I = 0; I < Summary.PreloadDependencyCount; ++I) { ProxyAr << CookedHeaderData.PreloadDependencies.AddDefaulted_GetRef(); } } if (Summary.ExportCount > 0) { CookedHeaderData.ObjectExports.Reserve(Summary.ExportCount); ProxyAr.Seek(Summary.ExportOffset); for (int32 I = 0; I < Summary.ExportCount; ++I) { FObjectExport& ObjectExport = CookedHeaderData.ObjectExports.AddDefaulted_GetRef(); ProxyAr << ObjectExport; } } if (Summary.CellImportCount > 0) { CookedHeaderData.CellImports.Reserve(Summary.CellImportCount); ProxyAr.Seek(Summary.CellImportOffset); for (int32 I = 0; I < Summary.CellImportCount; ++I) { FCellImport& CellImport = CookedHeaderData.CellImports.AddDefaulted_GetRef(); ProxyAr << CellImport; } } if (Summary.CellExportCount > 0) { CookedHeaderData.CellExports.Reserve(Summary.CellExportCount); ProxyAr.Seek(Summary.CellExportOffset); for (int32 I = 0; I < Summary.CellExportCount; ++I) { FCellExport& CellExport = CookedHeaderData.CellExports.AddDefaulted_GetRef(); ProxyAr << CellExport; } } if (Summary.SoftPackageReferencesCount > 0) { ProxyAr.Seek(Summary.SoftPackageReferencesOffset); CookedHeaderData.SoftPackageReferences.Reserve(Summary.SoftPackageReferencesCount); for (int32 I = 0; I < Summary.SoftPackageReferencesCount; ++I) { FName& SoftReference = CookedHeaderData.SoftPackageReferences.Emplace_GetRef(); ProxyAr << SoftReference; } } if (Summary.DataResourceOffset > 0) { ProxyAr.Seek(Summary.DataResourceOffset); FObjectDataResource::Serialize(ProxyAr, CookedHeaderData.DataResources); } return CookedHeaderData; } PRAGMA_ENABLE_DEPRECATION_WARNINGS void FPackageStoreOptimizer::ResolveImport(FPackageStorePackage::FUnresolvedImport* Imports, const FObjectImport* ObjectImports, int32 LocalImportIndex) const { FPackageStorePackage::FUnresolvedImport* Import = Imports + LocalImportIndex; if (Import->FullName.Len() == 0) { Import->FullName.Reserve(256); const FObjectImport* ObjectImport = ObjectImports + LocalImportIndex; if (ObjectImport->OuterIndex.IsNull()) { FName PackageName = ObjectImport->ObjectName; PackageName.AppendString(Import->FullName); Import->FullName.ToLowerInline(); Import->FromPackageName = PackageName; Import->FromPackageNameLen = Import->FullName.Len(); Import->bIsScriptImport = Import->FullName.StartsWith(TEXT("/Script/")); Import->bIsVerseVNIImport = !Import->bIsScriptImport && Import->FullName.Contains(TEXT("/_Verse/VNI")); Import->bIsImportOfPackage = true; } else { const int32 OuterIndex = ObjectImport->OuterIndex.ToImport(); ResolveImport(Imports, ObjectImports, OuterIndex); FPackageStorePackage::FUnresolvedImport* OuterImport = Imports + OuterIndex; check(OuterImport->FullName.Len() > 0); Import->bIsScriptImport = OuterImport->bIsScriptImport; Import->bIsVerseVNIImport = OuterImport->bIsVerseVNIImport; Import->FullName.Append(OuterImport->FullName); Import->FullName.AppendChar(TEXT('/')); ObjectImport->ObjectName.AppendString(Import->FullName); Import->FullName.ToLowerInline(); Import->FromPackageName = OuterImport->FromPackageName; Import->FromPackageNameLen = OuterImport->FromPackageNameLen; } } } void FPackageStoreOptimizer::AppendPathForPublicExportHash(UObject* Object, FStringBuilderBase& OutPath) { if (!Object) { return; } if (!Object->GetOuter()) { // Outermost package objects do not have a PublicExportHash Name; they use 0 for PublicExportHash // Write nothing for them. return; } constexpr int32 DirectorySeparatorLen = 1; const TCHAR DirectorySeparatorChar = '/'; TArray> PathNames; int32 PathNameLen = 0; for (UObject* Iter = Object; Iter->GetOuter(); Iter = Iter->GetOuter()) { FName PathName = Iter->GetFName(); PathNames.Add(PathName); PathNameLen += PathName.GetStringLength() + DirectorySeparatorLen; } if (PathNameLen <= 1) { // We should be writing at least 2 characters: /. Write nothing if we find an empty LeafName. return; } int32 InitialLength = OutPath.Len(); for (FName PathName : ReverseIterate(PathNames)) { OutPath << DirectorySeparatorChar << PathName; } // Down-case the characters we wrote; public export hash is based on lower-case TCHAR* OutPathData = OutPath.GetData(); int32 NewLength = OutPath.Len(); for (int32 N = InitialLength; N < NewLength; ++N) { OutPathData[N] = FChar::ToLower(OutPathData[N]); } } bool FPackageStoreOptimizer::TryGetPublicExportHash(FStringView PackageRelativeExportPath, uint64& OutPublicExportHash) { OutPublicExportHash = 0; // PackageRelativeExportHash should have been generated by GetNameForPublicExportHash or an equivalent function if (PackageRelativeExportPath.Len() == 0 || // PublicExportHash is not defined for the UPackage object PackageRelativeExportPath.Len() == 1 || // An object with an empty leaf name is an error, but we ignore it and treat it the same as a package PackageRelativeExportPath[0] != '/') // Invalid string; GetNameForPublicExportHash starts every relative path with / { return false; } OutPublicExportHash = GetPublicExportHash(PackageRelativeExportPath); return true; } uint64 FPackageStoreOptimizer::GetPublicExportHash(FStringView PackageRelativeExportPath) { check(PackageRelativeExportPath.Len() > 1); check(PackageRelativeExportPath[0] == '/'); return CityHash64(reinterpret_cast(PackageRelativeExportPath.GetData() + 1), (PackageRelativeExportPath.Len() - 1) * sizeof(TCHAR)); } #if WITH_VERSE_VM || defined(__INTELLISENSE__) uint64 FPackageStoreOptimizer::GetCellExportHash(Verse::VUniqueString* VersePath) { return GetCellExportHash(VersePath->AsStringView()); } #endif uint64 FPackageStoreOptimizer::GetCellExportHash(FUtf8StringView VersePath) { return CityHash64(reinterpret_cast(VersePath.GetData()), VersePath.Len()); } void FPackageStoreOptimizer::ProcessImports(const FCookedHeaderData& CookedHeaderData, FPackageStorePackage* Package, TArray& UnresolvedImports) const { int32 ImportCount = CookedHeaderData.ObjectImports.Num(); UnresolvedImports.SetNum(ImportCount); Package->Imports.SetNum(ImportCount); int32 CellImportCount = CookedHeaderData.CellImports.Num(); Package->CellImports.SetNum(CellImportCount); TSet ImportedPackageNames; for (int32 ImportIndex = 0; ImportIndex < ImportCount; ++ImportIndex) { ResolveImport(UnresolvedImports.GetData(), CookedHeaderData.ObjectImports.GetData(), ImportIndex); FPackageStorePackage::FUnresolvedImport& UnresolvedImport = UnresolvedImports[ImportIndex]; // For VerseVNI imports, we still need to add the package name to the imported package names for when the // type being referenced wasn't generated by UHT if (!UnresolvedImport.bIsScriptImport) { if (UnresolvedImport.bIsImportOfPackage) { ImportedPackageNames.Add(UnresolvedImport.FromPackageName); } } } for (int32 CellImportIndex = 0; CellImportIndex < CellImportCount; ++CellImportIndex) { const FCellImport& CellImport = CookedHeaderData.CellImports[CellImportIndex]; int32 ImportPackageIndex = CellImport.PackageIndex.ToImport(); ResolveImport(UnresolvedImports.GetData(), CookedHeaderData.ObjectImports.GetData(), ImportPackageIndex); FPackageStorePackage::FUnresolvedImport& PackageImport = UnresolvedImports[ImportPackageIndex]; if (!PackageImport.bIsScriptImport) { ImportedPackageNames.Add(PackageImport.FromPackageName); } } Package->ImportedPackages.Reserve(ImportedPackageNames.Num()); for (FName ImportedPackageName : ImportedPackageNames) { Package->ImportedPackages.Emplace(ImportedPackageName); } Algo::Sort(Package->ImportedPackages); for (int32 ImportIndex = 0; ImportIndex < ImportCount; ++ImportIndex) { FPackageStorePackage::FUnresolvedImport& UnresolvedImport = UnresolvedImports[ImportIndex]; bool bImportFromPackage = !UnresolvedImport.bIsImportOfPackage; if (UnresolvedImport.bIsScriptImport) { FPackageObjectIndex ScriptObjectIndex = FPackageObjectIndex::FromScriptPath(UnresolvedImport.FullName); if (!ScriptObjectsMap.Contains(ScriptObjectIndex)) { UE_LOG(LogPackageStoreOptimizer, Warning, TEXT("Package '%s' is referencing missing script import '%s'"), *Package->Name.ToString(), *UnresolvedImport.FullName); } Package->Imports[ImportIndex] = ScriptObjectIndex; bImportFromPackage = false; } else if (UnresolvedImport.bIsVerseVNIImport) { FPackageObjectIndex ScriptObjectIndex = FPackageObjectIndex::FromScriptPath(UnresolvedImport.FullName); bool bIsUHTGeneratedVerseVNIObject = ScriptObjectsMap.Contains(ScriptObjectIndex); if (bIsUHTGeneratedVerseVNIObject) { Package->Imports[ImportIndex] = ScriptObjectIndex; bImportFromPackage = false; } } if (bImportFromPackage) { bool bFoundPackageIndex = false; for (uint32 PackageIndex = 0, PackageCount = static_cast(Package->ImportedPackages.Num()); PackageIndex < PackageCount; ++PackageIndex) { if (UnresolvedImport.FromPackageName == Package->ImportedPackages[PackageIndex].Name) { FStringView PackageRelativeName = FStringView(UnresolvedImport.FullName).RightChop(UnresolvedImport.FromPackageNameLen); check(PackageRelativeName.Len()); FPackageImportReference PackageImportRef(PackageIndex, Package->ImportedPublicExportHashes.Num()); Package->Imports[ImportIndex] = FPackageObjectIndex::FromPackageImportRef(PackageImportRef); uint64 ExportHash = GetPublicExportHash(PackageRelativeName); Package->ImportedPublicExportHashes.Add(ExportHash); bFoundPackageIndex = true; break; } } check(bFoundPackageIndex); } } for (int32 CellImportIndex = 0; CellImportIndex < CellImportCount; ++CellImportIndex) { const FCellImport& CellImport = CookedHeaderData.CellImports[CellImportIndex]; int32 ImportPackageIndex = CellImport.PackageIndex.ToImport(); FPackageStorePackage::FUnresolvedImport& PackageImport = UnresolvedImports[ImportPackageIndex]; if (PackageImport.bIsScriptImport) { FPackageObjectIndex ScriptCellIndex = FPackageObjectIndex::FromVersePath(CellImport.VersePath); if (!ScriptCellsMap.Contains(ScriptCellIndex)) { #if WITH_VERSE_VM || defined(__INTELLISENSE__) FUtf8StringView VersePathUtf8 = CellImport.VersePath->AsStringView(); #else FUtf8StringView VersePathUtf8 = CellImport.VersePath; #endif FString VersePath = FString(VersePathUtf8); UE_LOG(LogPackageStoreOptimizer, Warning, TEXT("Package '%s' is referencing missing script import '%s'"), *Package->Name.ToString(), *VersePath); } Package->CellImports[CellImportIndex] = ScriptCellIndex; } else { bool bFoundPackageIndex = false; for (uint32 PackageIndex = 0, PackageCount = static_cast(Package->ImportedPackages.Num()); PackageIndex < PackageCount; ++PackageIndex) { if (PackageImport.FromPackageName == Package->ImportedPackages[PackageIndex].Name) { FPackageImportReference PackageImportRef(PackageIndex, Package->ImportedPublicExportHashes.Num()); Package->CellImports[CellImportIndex] = FPackageObjectIndex::FromPackageImportRef(PackageImportRef); uint64 CellExportHash = GetCellExportHash(CellImport.VersePath); Package->ImportedPublicExportHashes.Add(CellExportHash); bFoundPackageIndex = true; break; } } check(bFoundPackageIndex); } } } void FPackageStoreOptimizer::ResolveExport( FPackageStorePackage::FUnresolvedExport* Exports, const FObjectExport* ObjectExports, const int32 LocalExportIndex, const FName& PackageName, FPackageStorePackage::FUnresolvedImport* Imports, const FObjectImport* ObjectImports) const { FPackageStorePackage::FUnresolvedExport* Export = Exports + LocalExportIndex; if (Export->FullName.Len() == 0) { Export->FullName.Reserve(256); const FObjectExport* ObjectExport = ObjectExports + LocalExportIndex; if (ObjectExport->OuterIndex.IsNull()) { PackageName.AppendString(Export->FullName); Export->FullName.AppendChar(TEXT('/')); ObjectExport->ObjectName.AppendString(Export->FullName); Export->FullName.ToLowerInline(); check(Export->FullName.Len() > 0); } else { FString* OuterName = nullptr; if (ObjectExport->OuterIndex.IsExport()) { int32 OuterExportIndex = ObjectExport->OuterIndex.ToExport(); ResolveExport(Exports, ObjectExports, OuterExportIndex, PackageName, Imports, ObjectImports); OuterName = &Exports[OuterExportIndex].FullName; } else { check(Imports && ObjectImports); int32 OuterImportIndex = ObjectExport->OuterIndex.ToImport(); ResolveImport(Imports, ObjectImports, OuterImportIndex); OuterName = &Imports[OuterImportIndex].FullName; } check(OuterName && OuterName->Len() > 0); Export->FullName.Append(*OuterName); Export->FullName.AppendChar(TEXT('/')); ObjectExport->ObjectName.AppendString(Export->FullName); Export->FullName.ToLowerInline(); } } } void FPackageStoreOptimizer::ProcessExports(const FCookedHeaderData& CookedHeaderData, FPackageStorePackage* Package, FPackageStorePackage::FUnresolvedImport* Imports) const { int32 ExportCount = CookedHeaderData.ObjectExports.Num(); int32 CellExportCount = CookedHeaderData.CellExports.Num(); TArray UnresolvedExports; UnresolvedExports.SetNum(ExportCount + CellExportCount); Package->Exports.SetNum(ExportCount); Package->CellExports.SetNum(CellExportCount); Package->ExportGraphNodes.Reserve((ExportCount + CellExportCount) * 2); auto PackageObjectIdFromPackageIndex = [](const TArray& Imports, const FPackageIndex& PackageIndex) -> FPackageObjectIndex { if (PackageIndex.IsImport()) { return Imports[PackageIndex.ToImport()]; } if (PackageIndex.IsExport()) { return FPackageObjectIndex::FromExportIndex(PackageIndex.ToExport()); } return FPackageObjectIndex(); }; FString PackageNameStr = Package->Name.ToString(); TMap SeenPublicExportHashes; for (int32 ExportIndex = 0; ExportIndex < ExportCount; ++ExportIndex) { const FObjectExport& ObjectExport = CookedHeaderData.ObjectExports[ExportIndex]; FPackageStorePackage::FExport& Export = Package->Exports[ExportIndex]; FPackageStorePackage::FUnresolvedExport& UnresolvedExport = UnresolvedExports[ExportIndex]; Export.ObjectName = ObjectExport.ObjectName; Export.ObjectFlags = ObjectExport.ObjectFlags; check(ObjectExport.SerialOffset >= Package->CookedHeaderSize); Export.SerialOffset = ObjectExport.SerialOffset - Package->CookedHeaderSize; Export.SerialSize = ObjectExport.SerialSize; Export.bNotForClient = ObjectExport.bNotForClient; Export.bNotForServer = ObjectExport.bNotForServer; Export.bIsPublic = (Export.ObjectFlags & RF_Public) > 0 || ObjectExport.bGeneratePublicHash; ResolveExport(UnresolvedExports.GetData(), CookedHeaderData.ObjectExports.GetData(), ExportIndex, Package->Name, Imports, CookedHeaderData.ObjectImports.GetData()); if (Export.bIsPublic) { check(UnresolvedExport.FullName.Len() > 0); FStringView PackageRelativeName = FStringView(UnresolvedExport.FullName).RightChop(PackageNameStr.Len()); check(PackageRelativeName.Len()); Export.PublicExportHash = GetPublicExportHash(PackageRelativeName); const FPackageStorePackage::FUnresolvedExport* FindCollidingExport = SeenPublicExportHashes.FindRef(Export.PublicExportHash); if (FindCollidingExport) { UE_LOG(LogPackageStoreOptimizer, Fatal, TEXT("Export hash collision in package \"%s\": \"%s\" and \"%s"), *PackageNameStr, PackageRelativeName.GetData(), *FindCollidingExport->FullName); } SeenPublicExportHashes.Add(Export.PublicExportHash, &UnresolvedExport); } Export.OuterIndex = PackageObjectIdFromPackageIndex(Package->Imports, ObjectExport.OuterIndex); Export.ClassIndex = PackageObjectIdFromPackageIndex(Package->Imports, ObjectExport.ClassIndex); Export.SuperIndex = PackageObjectIdFromPackageIndex(Package->Imports, ObjectExport.SuperIndex); Export.TemplateIndex = PackageObjectIdFromPackageIndex(Package->Imports, ObjectExport.TemplateIndex); for (uint8 CommandType = 0; CommandType < FExportBundleEntry::ExportCommandType_Count; ++CommandType) { FPackageStorePackage::FExportGraphNode& Node = Package->ExportGraphNodes.AddDefaulted_GetRef(); Node.BundleEntry.CommandType = FExportBundleEntry::EExportCommandType(CommandType); Node.BundleEntry.LocalExportIndex = ExportIndex; Node.bIsPublic = Export.bIsPublic; Export.Nodes[CommandType] = &Node; } } TArrayView UnresolvedCellExports = UnresolvedExports; UnresolvedCellExports.RightChopInline(ExportCount); for (int32 CellExportIndex = 0; CellExportIndex < CellExportCount; ++CellExportIndex) { const FCellExport& CellExport = CookedHeaderData.CellExports[CellExportIndex]; FPackageStorePackage::FCellExport& Export = Package->CellExports[CellExportIndex]; FPackageStorePackage::FUnresolvedExport& UnresolvedExport = UnresolvedCellExports[CellExportIndex]; check(CellExport.SerialOffset >= Package->CookedHeaderSize); Export.SerialOffset = CellExport.SerialOffset - Package->CookedHeaderSize; Export.SerialLayoutSize = CellExport.SerialLayoutSize; Export.SerialSize = CellExport.SerialSize; #if WITH_VERSE_VM || defined(__INTELLISENSE__) FUtf8StringView VersePathUtf8 = CellExport.VersePath->AsStringView(); #else FUtf8StringView VersePathUtf8 = CellExport.VersePath; #endif if (!VersePathUtf8.IsEmpty()) { UnresolvedExport.FullName = FString(VersePathUtf8); Export.PublicExportHash = GetCellExportHash(CellExport.VersePath); if (const FPackageStorePackage::FUnresolvedExport* FindCollidingExport = SeenPublicExportHashes.FindRef(Export.PublicExportHash)) { UE_LOG(LogPackageStoreOptimizer, Fatal, TEXT("Export hash collision in package \"%s\": \"%s\" and \"%s\""), *PackageNameStr, *UnresolvedExport.FullName, *FindCollidingExport->FullName); } SeenPublicExportHashes.Add(Export.PublicExportHash, &UnresolvedExport); } #if WITH_VERSE_VM || defined(__INTELLISENSE__) Export.CppClassInfo = CellExport.CppClassInfo->Name; #else Export.CppClassInfo = CellExport.CppClassInfo; #endif for (uint8 CommandType = 0; CommandType < FExportBundleEntry::ExportCommandType_Count; ++CommandType) { FPackageStorePackage::FExportGraphNode& Node = Package->ExportGraphNodes.AddDefaulted_GetRef(); Node.BundleEntry.CommandType = FExportBundleEntry::EExportCommandType(CommandType); Node.BundleEntry.LocalExportIndex = ExportCount + CellExportIndex; Export.Nodes[CommandType] = &Node; } } } void FPackageStoreOptimizer::ProcessPreloadDependencies(const FCookedHeaderData& CookedHeaderData, FPackageStorePackage* Package) const { TRACE_CPUPROFILER_EVENT_SCOPE(ProcessPreloadDependencies); auto AddNodeDependency = [](FPackageStorePackage* Package, int32 FromExportIndex, FExportBundleEntry::EExportCommandType FromExportBundleCommandType, FPackageStorePackage::FExportGraphNode* ToNode) { FPackageStorePackage::FExportGraphNode* FromNode; if (FromExportIndex < Package->Exports.Num()) { FPackageStorePackage::FExport& FromExport = Package->Exports[FromExportIndex]; FromNode = FromExport.Nodes[FromExportBundleCommandType]; } else { int32 CellExportIndex = FromExportIndex - Package->Exports.Num(); FPackageStorePackage::FCellExport& FromExport = Package->CellExports[CellExportIndex]; FromNode = FromExport.Nodes[FromExportBundleCommandType]; } ToNode->InternalDependencies.Add(FromNode); }; auto IsScriptImport = [](FPackageStorePackage* Package, int32 ImportIndex) { if (ImportIndex < Package->Imports.Num()) { return Package->Imports[ImportIndex].IsScriptImport(); } else { int32 CellImportIndex = ImportIndex - Package->Imports.Num(); return Package->CellImports[CellImportIndex].IsScriptImport(); } }; TArray& DependencyBundleHeaders = Package->GraphData.DependencyBundleHeaders; TArray& DependencyBundleEntries = Package->GraphData.DependencyBundleEntries; for (int32 ExportIndex = 0; ExportIndex < Package->Exports.Num(); ++ExportIndex) { FPackageStorePackage::FExport& Export = Package->Exports[ExportIndex]; const FObjectExport& ObjectExport = CookedHeaderData.ObjectExports[ExportIndex]; AddNodeDependency(Package, ExportIndex, FExportBundleEntry::ExportCommandType_Create, Export.Nodes[FExportBundleEntry::ExportCommandType_Serialize]); FDependencyBundleHeader& DependencyBundleHeader = DependencyBundleHeaders.AddDefaulted_GetRef(); FMemory::Memzero(&DependencyBundleHeader, sizeof(FDependencyBundleHeader)); if (ObjectExport.FirstExportDependency >= 0) { DependencyBundleHeader.FirstEntryIndex = DependencyBundleEntries.Num(); int32 StartIndex = ObjectExport.FirstExportDependency + ObjectExport.SerializationBeforeSerializationDependencies + ObjectExport.CreateBeforeSerializationDependencies + ObjectExport.SerializationBeforeCreateDependencies; for (int32 Index = StartIndex; Index < StartIndex + ObjectExport.CreateBeforeCreateDependencies; ++Index) { FPackageIndex Dep = CookedHeaderData.PreloadDependencies[Index]; if (Dep.IsExport()) { AddNodeDependency(Package, Dep.ToExport(), FExportBundleEntry::ExportCommandType_Create, Export.Nodes[FExportBundleEntry::ExportCommandType_Create]); } if (Dep.IsExport() || !IsScriptImport(Package, Dep.ToImport())) { DependencyBundleEntries.AddDefaulted_GetRef().LocalImportOrExportIndex = Dep; ++DependencyBundleHeader.EntryCount[FExportBundleEntry::ExportCommandType_Create][FExportBundleEntry::ExportCommandType_Create]; } } StartIndex = ObjectExport.FirstExportDependency + ObjectExport.SerializationBeforeSerializationDependencies + ObjectExport.CreateBeforeSerializationDependencies; for (int32 Index = StartIndex; Index < StartIndex + ObjectExport.SerializationBeforeCreateDependencies; ++Index) { FPackageIndex Dep = CookedHeaderData.PreloadDependencies[Index]; if (Dep.IsExport()) { AddNodeDependency(Package, Dep.ToExport(), FExportBundleEntry::ExportCommandType_Serialize, Export.Nodes[FExportBundleEntry::ExportCommandType_Create]); } if (Dep.IsExport() || !IsScriptImport(Package, Dep.ToImport())) { DependencyBundleEntries.AddDefaulted_GetRef().LocalImportOrExportIndex = Dep; ++DependencyBundleHeader.EntryCount[FExportBundleEntry::ExportCommandType_Create][FExportBundleEntry::ExportCommandType_Serialize]; } } StartIndex = ObjectExport.FirstExportDependency + ObjectExport.SerializationBeforeSerializationDependencies; for (int32 Index = StartIndex; Index < StartIndex + ObjectExport.CreateBeforeSerializationDependencies; ++Index) { FPackageIndex Dep = CookedHeaderData.PreloadDependencies[Index]; if (Dep.IsExport()) { AddNodeDependency(Package, Dep.ToExport(), FExportBundleEntry::ExportCommandType_Create, Export.Nodes[FExportBundleEntry::ExportCommandType_Serialize]); } if (Dep.IsExport() || !IsScriptImport(Package, Dep.ToImport())) { DependencyBundleEntries.AddDefaulted_GetRef().LocalImportOrExportIndex = Dep; ++DependencyBundleHeader.EntryCount[FExportBundleEntry::ExportCommandType_Serialize][FExportBundleEntry::ExportCommandType_Create]; } } StartIndex = ObjectExport.FirstExportDependency; for (int32 Index = StartIndex; Index < StartIndex + ObjectExport.SerializationBeforeSerializationDependencies; ++Index) { FPackageIndex Dep = CookedHeaderData.PreloadDependencies[Index]; if (Dep.IsExport()) { AddNodeDependency(Package, Dep.ToExport(), FExportBundleEntry::ExportCommandType_Serialize, Export.Nodes[FExportBundleEntry::ExportCommandType_Serialize]); } if (Dep.IsExport() || !IsScriptImport(Package, Dep.ToImport())) { DependencyBundleEntries.AddDefaulted_GetRef().LocalImportOrExportIndex = Dep; ++DependencyBundleHeader.EntryCount[FExportBundleEntry::ExportCommandType_Serialize][FExportBundleEntry::ExportCommandType_Serialize]; } } } else { DependencyBundleHeader.FirstEntryIndex = -1; } } for (int32 CellExportIndex = 0; CellExportIndex < Package->CellExports.Num(); ++CellExportIndex) { FPackageStorePackage::FCellExport& Export = Package->CellExports[CellExportIndex]; const FCellExport& CellExport = CookedHeaderData.CellExports[CellExportIndex]; int ExportIndex = Package->Exports.Num() + CellExportIndex; AddNodeDependency(Package, ExportIndex, FExportBundleEntry::ExportCommandType_Create, Export.Nodes[FExportBundleEntry::ExportCommandType_Serialize]); FDependencyBundleHeader& DependencyBundleHeader = DependencyBundleHeaders.AddDefaulted_GetRef(); FMemory::Memzero(&DependencyBundleHeader, sizeof(FDependencyBundleHeader)); if (CellExport.FirstExportDependency >= 0) { DependencyBundleHeader.FirstEntryIndex = DependencyBundleEntries.Num(); int32 StartIndex = CellExport.FirstExportDependency + CellExport.SerializationBeforeSerializationDependencies; for (int32 Index = StartIndex; Index < StartIndex + CellExport.CreateBeforeSerializationDependencies; ++Index) { FPackageIndex Dep = CookedHeaderData.PreloadDependencies[Index]; if (Dep.IsExport()) { AddNodeDependency(Package, Dep.ToExport(), FExportBundleEntry::ExportCommandType_Create, Export.Nodes[FExportBundleEntry::ExportCommandType_Serialize]); } if (Dep.IsExport() || !IsScriptImport(Package, Dep.ToImport())) { DependencyBundleEntries.AddDefaulted_GetRef().LocalImportOrExportIndex = Dep; ++DependencyBundleHeader.EntryCount[FExportBundleEntry::ExportCommandType_Serialize][FExportBundleEntry::ExportCommandType_Create]; } } StartIndex = CellExport.FirstExportDependency; for (int32 Index = StartIndex; Index < StartIndex + CellExport.SerializationBeforeSerializationDependencies; ++Index) { FPackageIndex Dep = CookedHeaderData.PreloadDependencies[Index]; if (Dep.IsExport()) { AddNodeDependency(Package, Dep.ToExport(), FExportBundleEntry::ExportCommandType_Serialize, Export.Nodes[FExportBundleEntry::ExportCommandType_Serialize]); } if (Dep.IsExport() || !IsScriptImport(Package, Dep.ToImport())) { DependencyBundleEntries.AddDefaulted_GetRef().LocalImportOrExportIndex = Dep; ++DependencyBundleHeader.EntryCount[FExportBundleEntry::ExportCommandType_Serialize][FExportBundleEntry::ExportCommandType_Serialize]; } } } else { DependencyBundleHeader.FirstEntryIndex = -1; } } } void FPackageStoreOptimizer::ProcessDataResources(const FCookedHeaderData& CookedHeaderData, FPackageStorePackage* Package) const { for (const FObjectDataResource& DataResource : CookedHeaderData.DataResources) { FBulkDataMapEntry& Entry = Package->BulkDataEntries.AddDefaulted_GetRef(); checkf(DataResource.SerialSize == DataResource.RawSize, TEXT("Compressed bulk data is not supported in cooked builds")); Entry.SerialOffset = DataResource.SerialOffset; Entry.DuplicateSerialOffset = DataResource.DuplicateSerialOffset; Entry.SerialSize = DataResource.SerialSize; Entry.Flags = DataResource.LegacyBulkDataFlags; Entry.CookedIndex = DataResource.CookedIndex; } } TArray FPackageStoreOptimizer::SortExportGraphNodesInLoadOrder(FPackageStorePackage* Package, FExportGraphEdges& Edges) const { TRACE_CPUPROFILER_EVENT_SCOPE(SortExportGraphNodesInLoadOrder); int32 NodeCount = Package->ExportGraphNodes.Num(); for (auto& KV : Edges) { FPackageStorePackage::FExportGraphNode* ToNode = KV.Value; ++ToNode->IncomingEdgeCount; } auto NodeSorter = [](const FPackageStorePackage::FExportGraphNode& A, const FPackageStorePackage::FExportGraphNode& B) { if (A.bIsPublic != B.bIsPublic) { return A.bIsPublic; } if (A.BundleEntry.CommandType != B.BundleEntry.CommandType) { return A.BundleEntry.CommandType < B.BundleEntry.CommandType; } return A.BundleEntry.LocalExportIndex < B.BundleEntry.LocalExportIndex; }; TArray NodesWithNoIncomingEdges; NodesWithNoIncomingEdges.Reserve(NodeCount); for (FPackageStorePackage::FExportGraphNode& Node : Package->ExportGraphNodes) { if (Node.IncomingEdgeCount == 0) { NodesWithNoIncomingEdges.HeapPush(&Node, NodeSorter); } } TArray LoadOrder; LoadOrder.Reserve(NodeCount); while (NodesWithNoIncomingEdges.Num()) { FPackageStorePackage::FExportGraphNode* RemovedNode; NodesWithNoIncomingEdges.HeapPop(RemovedNode, NodeSorter, EAllowShrinking::No); LoadOrder.Add(RemovedNode); for (auto EdgeIt = Edges.CreateKeyIterator(RemovedNode); EdgeIt; ++EdgeIt) { FPackageStorePackage::FExportGraphNode* ToNode = EdgeIt.Value(); check(ToNode->IncomingEdgeCount > 0); if (--ToNode->IncomingEdgeCount == 0) { NodesWithNoIncomingEdges.HeapPush(ToNode, NodeSorter); } EdgeIt.RemoveCurrent(); } } check(LoadOrder.Num() == NodeCount); return LoadOrder; } void FPackageStoreOptimizer::CreateExportBundle(FPackageStorePackage* Package) const { TRACE_CPUPROFILER_EVENT_SCOPE(CreateExportBundles); FExportGraphEdges Edges; for (FPackageStorePackage::FExportGraphNode& ExportGraphNode : Package->ExportGraphNodes) { for (FPackageStorePackage::FExportGraphNode* InternalDependency : ExportGraphNode.InternalDependencies) { Edges.Add(InternalDependency, &ExportGraphNode); } } TArray LoadOrder = SortExportGraphNodesInLoadOrder(Package, Edges); for (FPackageStorePackage::FExportGraphNode* Node : LoadOrder) { Package->GraphData.ExportBundleEntries.Add(Node->BundleEntry); } } void FPackageStoreOptimizer::FinalizePackageHeader(FPackageStorePackage* Package) const { FBufferWriter ImportedPublicExportHashesArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); for (uint64 ImportedPublicExportHash : Package->ImportedPublicExportHashes) { ImportedPublicExportHashesArchive << ImportedPublicExportHash; } uint64 ImportedPublicExportHashesSize = ImportedPublicExportHashesArchive.Tell(); FBufferWriter ImportMapArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); for (FPackageObjectIndex Import : Package->Imports) { ImportMapArchive << Import; } uint64 ImportMapSize = ImportMapArchive.Tell(); FBufferWriter ExportMapArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); for (const FPackageStorePackage::FExport& Export : Package->Exports) { FExportMapEntry ExportMapEntry; ExportMapEntry.CookedSerialOffset = Export.SerialOffset; ExportMapEntry.CookedSerialSize = Export.SerialSize; Package->NameMapBuilder.MarkNameAsReferenced(Export.ObjectName); ExportMapEntry.ObjectName = Package->NameMapBuilder.MapName(Export.ObjectName); ExportMapEntry.PublicExportHash = Export.PublicExportHash; ExportMapEntry.OuterIndex = Export.OuterIndex; ExportMapEntry.ClassIndex = Export.ClassIndex; ExportMapEntry.SuperIndex = Export.SuperIndex; ExportMapEntry.TemplateIndex = Export.TemplateIndex; ExportMapEntry.ObjectFlags = Export.ObjectFlags; ExportMapEntry.FilterFlags = EExportFilterFlags::None; if (Export.bNotForClient) { ExportMapEntry.FilterFlags = EExportFilterFlags::NotForClient; } else if (Export.bNotForServer) { ExportMapEntry.FilterFlags = EExportFilterFlags::NotForServer; } ExportMapArchive << ExportMapEntry; } uint64 ExportMapSize = ExportMapArchive.Tell(); FBufferWriter CellImportMapArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); for (FPackageObjectIndex& CellImport : Package->CellImports) { CellImportMapArchive << CellImport; } uint64 CellImportMapSize = CellImportMapArchive.Tell(); FBufferWriter CellExportMapArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); for (const FPackageStorePackage::FCellExport& CellExport : Package->CellExports) { FCellExportMapEntry CellExportMapEntry; CellExportMapEntry.CookedSerialOffset = CellExport.SerialOffset; CellExportMapEntry.CookedSerialLayoutSize = CellExport.SerialLayoutSize; CellExportMapEntry.CookedSerialSize = CellExport.SerialSize; CellExportMapEntry.PublicExportHash = CellExport.PublicExportHash; Package->NameMapBuilder.MarkNameAsReferenced(CellExport.CppClassInfo); CellExportMapEntry.CppClassInfo = Package->NameMapBuilder.MapName(CellExport.CppClassInfo); CellExportMapArchive << CellExportMapEntry; } uint64 CellExportMapSize = CellExportMapArchive.Tell(); FBufferWriter ExportBundleEntriesArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); for (FExportBundleEntry BundleEntry : Package->GraphData.ExportBundleEntries) { ExportBundleEntriesArchive << BundleEntry; } uint64 ExportBundleEntriesSize = ExportBundleEntriesArchive.Tell(); FBufferWriter DependencyBundleHeadersArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); for (FDependencyBundleHeader DependencyBundleHeader : Package->GraphData.DependencyBundleHeaders) { DependencyBundleHeadersArchive << DependencyBundleHeader; } FBufferWriter DependencyBundleEntriesArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); for (FDependencyBundleEntry DependencyBundleEntry : Package->GraphData.DependencyBundleEntries) { DependencyBundleEntriesArchive << DependencyBundleEntry; } uint64 GraphDataSize = DependencyBundleHeadersArchive.Tell() + DependencyBundleEntriesArchive.Tell(); Package->NameMapBuilder.MarkNameAsReferenced(Package->Name); FMappedName MappedPackageName = Package->NameMapBuilder.MapName(Package->Name); FZenPackageImportedPackageNamesContainer ImportedPackageNamesContainer; ImportedPackageNamesContainer.Names.Reserve(Package->ImportedPackages.Num()); for (const FPackageStorePackage::FImportedPackageRef& ImportedPackage : Package->ImportedPackages) { ImportedPackageNamesContainer.Names.Add(ImportedPackage.Name); } FBufferWriter ImportedPackagesArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); ImportedPackagesArchive << ImportedPackageNamesContainer; uint64 ImportedPackagesSize = ImportedPackagesArchive.Tell(); FBufferWriter NameMapArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); SaveNameBatch(Package->NameMapBuilder.GetNameMap(), NameMapArchive); uint64 NameMapSize = NameMapArchive.Tell(); FBufferWriter VersioningInfoArchive(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); if (Package->VersioningInfo.IsSet()) { VersioningInfoArchive << Package->VersioningInfo.GetValue(); } uint64 VersioningInfoSize = VersioningInfoArchive.Tell(); FBufferWriter BulkDataMapAr(nullptr, 0, EBufferWriterFlags::AllowResize | EBufferWriterFlags::TakeOwnership); for (FBulkDataMapEntry& Entry : Package->BulkDataEntries) { BulkDataMapAr << Entry; } uint64 BulkDataMapSize = BulkDataMapAr.Tell(); uint64 BulkDataPad = 0; uint64 OffsetBeforeBulkDataMap = sizeof(FZenPackageSummary) + VersioningInfoSize + sizeof(FZenPackageCellOffsets) + NameMapSize + sizeof(BulkDataPad); uint64 AlignedOffsetBeforeBulkDataMap = Align(OffsetBeforeBulkDataMap, sizeof(uint64)); BulkDataPad = AlignedOffsetBeforeBulkDataMap - OffsetBeforeBulkDataMap; uint64 OffsetBeforePublicExportHashes = AlignedOffsetBeforeBulkDataMap + BulkDataMapSize + sizeof(BulkDataMapSize); uint64 AlignedOffsetBeforePublicExportHashes = Align(OffsetBeforePublicExportHashes, sizeof(uint64)); uint64 HeaderSize = AlignedOffsetBeforePublicExportHashes + ImportedPublicExportHashesSize + ImportMapSize + ExportMapSize + CellImportMapSize + CellExportMapSize + ExportBundleEntriesSize + GraphDataSize + ImportedPackagesSize; Package->HeaderBuffer = FIoBuffer(HeaderSize); uint8* HeaderData = Package->HeaderBuffer.Data(); FMemory::Memzero(HeaderData, HeaderSize); FZenPackageSummary* PackageSummary = reinterpret_cast(HeaderData); PackageSummary->HeaderSize = HeaderSize; PackageSummary->Name = MappedPackageName; PackageSummary->PackageFlags = Package->PackageFlags; PackageSummary->CookedHeaderSize = Package->CookedHeaderSize; FBufferWriter HeaderArchive(HeaderData, HeaderSize); HeaderArchive.Seek(sizeof(FZenPackageSummary)); if (Package->VersioningInfo.IsSet()) { PackageSummary->bHasVersioningInfo = 1; HeaderArchive.Serialize(VersioningInfoArchive.GetWriterData(), VersioningInfoArchive.Tell()); } else { PackageSummary->bHasVersioningInfo = 0; } FZenPackageCellOffsets* CellOffsets = HeaderArchive.SerializeUninitialized(); HeaderArchive.Serialize(NameMapArchive.GetWriterData(), NameMapArchive.Tell()); HeaderArchive << BulkDataPad; if (BulkDataPad > 0) { uint8 PadBytes[sizeof(uint64)] = {}; HeaderArchive.Serialize(PadBytes, BulkDataPad); } check(HeaderArchive.Tell() == AlignedOffsetBeforeBulkDataMap); HeaderArchive << BulkDataMapSize; HeaderArchive.Serialize(BulkDataMapAr.GetWriterData(), BulkDataMapSize); if (uint64 Pad=AlignedOffsetBeforePublicExportHashes-OffsetBeforePublicExportHashes; Pad > 0) { uint8 PadBytes[sizeof(uint64)] = {}; HeaderArchive.Serialize(PadBytes, Pad); } check(HeaderArchive.Tell() == AlignedOffsetBeforePublicExportHashes); // raw arrays of 8-byte aligned items PackageSummary->ImportedPublicExportHashesOffset = HeaderArchive.Tell(); HeaderArchive.Serialize(ImportedPublicExportHashesArchive.GetWriterData(), ImportedPublicExportHashesArchive.Tell()); PackageSummary->ImportMapOffset = HeaderArchive.Tell(); HeaderArchive.Serialize(ImportMapArchive.GetWriterData(), ImportMapArchive.Tell()); PackageSummary->ExportMapOffset = HeaderArchive.Tell(); HeaderArchive.Serialize(ExportMapArchive.GetWriterData(), ExportMapArchive.Tell()); CellOffsets->CellImportMapOffset = HeaderArchive.Tell(); HeaderArchive.Serialize(CellImportMapArchive.GetWriterData(), CellImportMapArchive.Tell()); CellOffsets->CellExportMapOffset = HeaderArchive.Tell(); HeaderArchive.Serialize(CellExportMapArchive.GetWriterData(), CellExportMapArchive.Tell()); // raw arrays of 4-byte aligned items PackageSummary->ExportBundleEntriesOffset = HeaderArchive.Tell(); HeaderArchive.Serialize(ExportBundleEntriesArchive.GetWriterData(), ExportBundleEntriesArchive.Tell()); PackageSummary->DependencyBundleHeadersOffset = HeaderArchive.Tell(); HeaderArchive.Serialize(DependencyBundleHeadersArchive.GetWriterData(), DependencyBundleHeadersArchive.Tell()); PackageSummary->DependencyBundleEntriesOffset = HeaderArchive.Tell(); HeaderArchive.Serialize(DependencyBundleEntriesArchive.GetWriterData(), DependencyBundleEntriesArchive.Tell()); PackageSummary->ImportedPackageNamesOffset = HeaderArchive.Tell(); HeaderArchive.Serialize(ImportedPackagesArchive.GetWriterData(), ImportedPackagesArchive.Tell()); check(HeaderArchive.Tell() == PackageSummary->HeaderSize) } FIoBuffer FPackageStoreOptimizer::CreatePackageBuffer(const FPackageStorePackage* Package, const FIoBuffer& CookedExportsDataBuffer) const { check(Package->HeaderBuffer.DataSize() > 0); const uint64 BundleBufferSize = Package->HeaderBuffer.DataSize() + CookedExportsDataBuffer.DataSize(); FIoBuffer BundleBuffer(BundleBufferSize); FMemory::Memcpy(BundleBuffer.Data(), Package->HeaderBuffer.Data(), Package->HeaderBuffer.DataSize()); FMemory::Memcpy(BundleBuffer.Data() + Package->HeaderBuffer.DataSize(), CookedExportsDataBuffer.Data(), CookedExportsDataBuffer.DataSize()); return BundleBuffer; } void FPackageStoreOptimizer::FindScriptObjectsRecursive( TMap& OutScriptObjectsMap, FPackageObjectIndex OuterIndex, bool bOuterIsVerseVNI, UObject* Object) { if (!Object->HasAllFlags(RF_Public)) { UE_LOG(LogPackageStoreOptimizer, Verbose, TEXT("Skipping script object: %s (!RF_Public)"), *Object->GetFullName()); return; } FString OuterFullName; FPackageObjectIndex OuterCDOClassIndex; { const FScriptObjectData* Outer = OutScriptObjectsMap.Find(OuterIndex); check(Outer); OuterFullName = Outer->FullName; OuterCDOClassIndex = Outer->CDOClassIndex; } // Unlike things in /Scripts/, with Verse VNI objects, there is a mix of UHT generated types, which will always be // available, and Verse compiler generated types which need to be cooked and packaged. We don't want the compiler // generated types to be included in this collection. if (bOuterIsVerseVNI && !Verse::VerseVM::IsUHTGeneratedVerseVNIObject(Object)) { return; } FName ObjectName = Object->GetFName(); FString TempFullName = OuterFullName; TempFullName.AppendChar(TEXT('/')); ObjectName.AppendString(TempFullName); TempFullName.ToLowerInline(); FPackageObjectIndex GlobalImportIndex = FPackageObjectIndex::FromScriptPath(TempFullName); FScriptObjectData* ScriptImport = OutScriptObjectsMap.Find(GlobalImportIndex); if (ScriptImport) { UE_LOG(LogPackageStoreOptimizer, Fatal, TEXT("Import name hash collision \"%s\" and \"%s"), *TempFullName, *ScriptImport->FullName); } FPackageObjectIndex CDOClassIndex = OuterCDOClassIndex; if (CDOClassIndex.IsNull()) { TCHAR NameBuffer[FName::StringBufferSize]; uint32 Len = ObjectName.ToString(NameBuffer); if (FCString::Strncmp(NameBuffer, TEXT("Default__"), 9) == 0) { FString CDOClassFullName = OuterFullName; CDOClassFullName.AppendChar(TEXT('/')); CDOClassFullName.AppendChars(NameBuffer + 9, Len - 9); CDOClassFullName.ToLowerInline(); CDOClassIndex = FPackageObjectIndex::FromScriptPath(CDOClassFullName); } } ScriptImport = &OutScriptObjectsMap.Add(GlobalImportIndex); ScriptImport->GlobalIndex = GlobalImportIndex; ScriptImport->FullName = MoveTemp(TempFullName); ScriptImport->OuterIndex = OuterIndex; ScriptImport->ObjectName = ObjectName; ScriptImport->CDOClassIndex = CDOClassIndex; TArray InnerObjects; GetObjectsWithOuter(Object, InnerObjects, /*bIncludeNestedObjects*/false); for (UObject* InnerObject : InnerObjects) { FindScriptObjectsRecursive(OutScriptObjectsMap, GlobalImportIndex, bOuterIsVerseVNI, InnerObject); } }; void FPackageStoreOptimizer::FindScriptObjects() { FindScriptObjects(ScriptObjectsMap); TotalScriptObjectCount = ScriptObjectsMap.Num(); #if WITH_VERSE_VM || defined(__INTELLISENSE__) if (Verse::VPackage* BuiltInPackage = Verse::GlobalProgram->LookupPackage("$BuiltIn")) { for (int32 Index = 0; Index < BuiltInPackage->NumDefinitions(); ++Index) { Verse::VUniqueString& VersePath = BuiltInPackage->GetDefinitionName(Index); FPackageObjectIndex GlobalImportIndex = FPackageObjectIndex::FromVersePath(VersePath.AsStringView()); FScriptCellData* ScriptImport = ScriptCellsMap.Find(GlobalImportIndex); if (ScriptImport) { UE_LOG(LogPackageStoreOptimizer, Fatal, TEXT("Import name hash collision \"%s\" and \"%s\""), *VersePath.AsString(), *FString(ScriptImport->VersePath)); } ScriptImport = &ScriptCellsMap.Add(GlobalImportIndex); ScriptImport->GlobalIndex = GlobalImportIndex; ScriptImport->VersePath = VersePath.AsStringView(); } } #endif } void FPackageStoreOptimizer::FindScriptObjects(TMap& OutScriptObjectsMap) { TRACE_CPUPROFILER_EVENT_SCOPE(FindScriptObjects); FRuntimeScriptPackages ScriptPackages; FindAllRuntimeScriptPackages(ScriptPackages); TArray InnerObjects; auto FindScriptObjectsInPackages = [&OutScriptObjectsMap, &InnerObjects](const TArray& Packages, bool bIsVerseVNI) { for (UPackage* Package : Packages) { FName ObjectName = Package->GetFName(); FString FullName = Package->GetName(); FullName.ToLowerInline(); FPackageObjectIndex GlobalImportIndex = FPackageObjectIndex::FromScriptPath(FullName); FScriptObjectData* ScriptImport = OutScriptObjectsMap.Find(GlobalImportIndex); checkf(!ScriptImport, TEXT("Import name hash collision \"%s\" and \"%s"), *FullName, *ScriptImport->FullName); ScriptImport = &OutScriptObjectsMap.Add(GlobalImportIndex); ScriptImport->GlobalIndex = GlobalImportIndex; ScriptImport->FullName = FullName; ScriptImport->OuterIndex = FPackageObjectIndex(); ScriptImport->ObjectName = ObjectName; InnerObjects.Reset(); GetObjectsWithOuter(Package, InnerObjects, /*bIncludeNestedObjects*/false); for (UObject* InnerObject : InnerObjects) { FindScriptObjectsRecursive(OutScriptObjectsMap, GlobalImportIndex, bIsVerseVNI, InnerObject); } } }; FindScriptObjectsInPackages(ScriptPackages.Script, false); FindScriptObjectsInPackages(ScriptPackages.VerseVNI, true); } FIoBuffer FPackageStoreOptimizer::CreateScriptObjectsBuffer() const { TArray ScriptObjectsAsArray; ScriptObjectsMap.GenerateValueArray(ScriptObjectsAsArray); Algo::Sort(ScriptObjectsAsArray, [](const FScriptObjectData& A, const FScriptObjectData& B) { return A.FullName < B.FullName; }); TArray ScriptObjectEntries; ScriptObjectEntries.Reserve(ScriptObjectsAsArray.Num()); FPackageStoreNameMapBuilder NameMapBuilder; NameMapBuilder.SetNameMapType(FMappedName::EType::Global); for (const FScriptObjectData& ImportData : ScriptObjectsAsArray) { NameMapBuilder.MarkNameAsReferenced(ImportData.ObjectName); FScriptObjectEntry& Entry = ScriptObjectEntries.AddDefaulted_GetRef(); Entry.Mapped = NameMapBuilder.MapName(ImportData.ObjectName); Entry.GlobalIndex = ImportData.GlobalIndex; Entry.OuterIndex = ImportData.OuterIndex; Entry.CDOClassIndex = ImportData.CDOClassIndex; } FLargeMemoryWriter ScriptObjectsArchive(0, true); SaveNameBatch(NameMapBuilder.GetNameMap(), ScriptObjectsArchive); int32 NumScriptObjects = ScriptObjectEntries.Num(); ScriptObjectsArchive << NumScriptObjects; for (FScriptObjectEntry& Entry : ScriptObjectEntries) { ScriptObjectsArchive << Entry; } int64 DataSize = ScriptObjectsArchive.TotalSize(); return FIoBuffer(FIoBuffer::AssumeOwnership, ScriptObjectsArchive.ReleaseOwnership(), DataSize); } void FPackageStoreOptimizer::LoadScriptObjectsBuffer(const FIoBuffer& ScriptObjectsBuffer) { TRACE_CPUPROFILER_EVENT_SCOPE(LoadScriptObjectsBuffer); FLargeMemoryReader ScriptObjectsArchive(ScriptObjectsBuffer.Data(), ScriptObjectsBuffer.DataSize()); TArray NameMap = LoadNameBatch(ScriptObjectsArchive); int32 NumScriptObjects; ScriptObjectsArchive << NumScriptObjects; for (int32 Index = 0; Index < NumScriptObjects; ++Index) { FScriptObjectEntry Entry{}; ScriptObjectsArchive << Entry; FScriptObjectData& ImportData = ScriptObjectsMap.Add(Entry.GlobalIndex); FMappedName MappedName = Entry.Mapped; ImportData.ObjectName = NameMap[MappedName.GetIndex()].ToName(MappedName.GetNumber()); ImportData.GlobalIndex = Entry.GlobalIndex; ImportData.OuterIndex = Entry.OuterIndex; ImportData.CDOClassIndex = Entry.CDOClassIndex; } } FPackageStoreEntryResource FPackageStoreOptimizer::CreatePackageStoreEntry(const FPackageStorePackage* Package, const FPackageStorePackage* OptionalSegmentPackage) const { FPackageStoreEntryResource Result; Result.Flags = EPackageStoreEntryFlags::HasPackageData; if (OptionalSegmentPackage) { Result.Flags |= EPackageStoreEntryFlags::OptionalSegment; if (OptionalSegmentPackage->HasEditorData()) { // AutoOptional packages are saved with editor data included Result.Flags |= EPackageStoreEntryFlags::AutoOptional; } } if (Package->PackageFlags & PKG_LoadUncooked) { Result.Flags |= EPackageStoreEntryFlags::LoadUncooked; } Result.PackageName = Package->Name; Result.PackageId = FPackageId::FromName(Package->Name); Result.ImportedPackageIds.Reserve(Package->ImportedPackages.Num()); for (const FPackageStorePackage::FImportedPackageRef& ImportedPackage : Package->ImportedPackages) { Result.ImportedPackageIds.Add(ImportedPackage.Id); } if (OptionalSegmentPackage) { Result.OptionalSegmentImportedPackageIds.Reserve(OptionalSegmentPackage->ImportedPackages.Num()); for (const FPackageStorePackage::FImportedPackageRef& ImportedPackage : OptionalSegmentPackage->ImportedPackages) { Result.OptionalSegmentImportedPackageIds.Add(ImportedPackage.Id); } } Result.SoftPackageReferences.Reserve(Package->SoftPackageReferences.Num()); for (const FName& SoftRefName : Package->SoftPackageReferences) { TCHAR NameStr[FName::StringBufferSize]; const uint32 NameLen = SoftRefName.ToString(NameStr); const FStringView SoftRef(NameStr, NameLen); if (!FPackageName::IsScriptPackage(SoftRef)) { if (!FPackageName::IsValidLongPackageName(SoftRef)) { UE_LOG(LogPackageStoreOptimizer, Display, TEXT("Skipping invalid soft package reference name '%s'"), *SoftRefName.ToString()); continue; } Result.SoftPackageReferences.Add(FPackageId::FromName(SoftRefName)); } } Result.SoftPackageReferences.Sort(); return Result; }