EM_Task/CoreUObject/Private/UObject/Package.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

304 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UObject/Package.h"
#include "HAL/FileManager.h"
#include "Misc/AssetRegistryInterface.h"
#include "Misc/ITransaction.h"
#include "Misc/PackageName.h"
#include "UObject/LinkerLoad.h"
#include "UObject/LinkerManager.h"
#include "UObject/MetaData.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectThreadContext.h"
/*-----------------------------------------------------------------------------
UPackage.
-----------------------------------------------------------------------------*/
/** Delegate to notify subscribers when a package is about to be saved. */
UPackage::FPreSavePackage UPackage::PreSavePackageEvent;
/** Delegate to notify subscribers when a package has been saved. This is triggered when the package saving
* has completed and was successful. */
UPackage::FOnPackageSaved UPackage::PackageSavedEvent;
/** Delegate to notify subscribers when the dirty state of a package is changed.
* Allows the editor to register the modified package as one that should be prompted for source control checkout.
* Use Package->IsDirty() to get the updated dirty state of the package */
UPackage::FOnPackageDirtyStateChanged UPackage::PackageDirtyStateChangedEvent;
/**
* Delegate to notify subscribers when a package is marked as dirty via UObjectBaseUtilty::MarkPackageDirty
* Note: Unlike FOnPackageDirtyStateChanged, this is always called, even when the package is already dirty
* Use bWasDirty to check the previous dirty state of the package
* Use Package->IsDirty() to get the updated dirty state of the package
*/
UPackage::FOnPackageMarkedDirty UPackage::PackageMarkedDirtyEvent;
void UPackage::PostInitProperties()
{
Super::PostInitProperties();
if (!HasAnyFlags(RF_ClassDefaultObject))
{
bDirty = false;
}
#if WITH_EDITORONLY_DATA
MetaData = nullptr;
PersistentGuid = FGuid::NewGuid();
#endif
LinkerPackageVersion = GPackageFileUE4Version;
LinkerLicenseeVersion = GPackageFileLicenseeUE4Version;
PIEInstanceID = INDEX_NONE;
#if WITH_EDITORONLY_DATA
bIsCookedForEditor = false;
// Mark this package as editor-only by default. As soon as something in it is accessed through a non editor-only
// property the flag will be removed.
bLoadedByEditorPropertiesOnly = !HasAnyFlags(RF_ClassDefaultObject) && !HasAnyPackageFlags(PKG_CompiledIn) && (IsRunningCommandlet());
#endif
}
/**
* Marks/Unmarks the package's bDirty flag
*/
void UPackage::SetDirtyFlag(bool bIsDirty)
{
if (GetOutermost() != GetTransientPackage())
{
if (GUndo != NULL
// PIE and script/class packages should never end up in the transaction buffer as we cannot undo during gameplay.
&& !GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor | PKG_ContainsScript | PKG_CompiledIn))
{
// make sure we're marked as transactional
SetFlags(RF_Transactional);
// don't call Modify() since it calls SetDirtyFlag()
GUndo->SaveObject(this);
}
// Update dirty bit
const bool bWasDirty = bDirty;
bDirty = bIsDirty;
if (bWasDirty != bIsDirty // Only fire the callback if the dirty state actually changes
&& GIsEditor // Only fire the callback in editor mode
&& !HasAnyPackageFlags(PKG_ContainsScript) // Skip script packages
&& !HasAnyPackageFlags(PKG_PlayInEditor) // Skip packages for PIE
&& GetTransientPackage() != this) // Skip the transient package
{
// Package is changing dirty state, let the editor know so we may prompt for source control checkout
PackageDirtyStateChangedEvent.Broadcast(this);
}
}
}
/**
* Serializer
* Save the value of bDirty into the transaction buffer, so that undo/redo will also mark/unmark the package as dirty, accordingly
*/
void UPackage::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
if (Ar.IsTransacting())
{
bool bTempDirty = bDirty;
Ar << bTempDirty;
bDirty = bTempDirty;
}
if (Ar.IsCountingMemory())
{
if (LinkerLoad)
{
FLinker* Loader = LinkerLoad;
Loader->Serialize(Ar);
}
}
}
UObject* UPackage::FindAssetInPackage() const
{
UObject* Asset = nullptr;
ForEachObjectWithPackage(this, [&Asset](UObject* Object)
{
if (Object->IsAsset() && !UE::AssetRegistry::FFiltering::ShouldSkipAsset(Object))
{
ensure(Asset == nullptr);
Asset = Object;
return false;
}
return true;
},
false);
return Asset;
}
TArray<UPackage*> UPackage::GetExternalPackages() const
{
TArray<UPackage*> Result;
TArray<UObject*> TopLevelObjects;
GetObjectsWithPackage(const_cast<UPackage*>(this), TopLevelObjects, false);
for (UObject* Object: TopLevelObjects)
{
ForEachObjectWithOuter(Object, [&Result, ThisPackage = this](UObject* InObject)
{
UPackage* ObjectPackage = InObject->GetExternalPackage();
if (ObjectPackage && ObjectPackage != ThisPackage)
{
Result.Add(ObjectPackage);
}
});
}
return Result;
}
/**
* Gets (after possibly creating) a metadata object for this package
*
* @return A valid UMetaData pointer for all objects in this package
*/
UMetaData* UPackage::GetMetaData()
{
checkf(!FPlatformProperties::RequiresCookedData(), TEXT("MetaData is only allowed in the Editor."));
#if WITH_EDITORONLY_DATA
// If there is no MetaData, try to find it.
if (MetaData == NULL)
{
MetaData = FindObjectFast<UMetaData>(this, FName(NAME_PackageMetaData));
// If MetaData is NULL then it wasn't loaded by linker, so we have to create it.
if (MetaData == NULL)
{
MetaData = NewObject<UMetaData>(this, NAME_PackageMetaData, RF_Standalone | RF_LoadCompleted);
}
}
check(MetaData);
if (MetaData->HasAnyFlags(RF_NeedLoad))
{
FLinkerLoad* MetaDataLinker = MetaData->GetLinker();
check(MetaDataLinker);
MetaDataLinker->Preload(MetaData);
}
return MetaData;
#else
return nullptr;
#endif
}
/**
* Fully loads this package. Safe to call multiple times and won't clobber already loaded assets.
*/
void UPackage::FullyLoad()
{
// Make sure we're a topmost package.
checkf(GetOuter() == nullptr, TEXT("Package is not topmost. Name:%s Path: %s"), *GetName(), *GetPathName());
// Only perform work if we're not already fully loaded.
if (!IsFullyLoaded())
{
// Re-load this package.
LoadPackage(nullptr, *GetName(), LOAD_None);
}
}
/** Tags generated objects with flags */
void UPackage::TagSubobjects(EObjectFlags NewFlags)
{
Super::TagSubobjects(NewFlags);
#if WITH_EDITORONLY_DATA
if (MetaData)
{
MetaData->SetFlags(NewFlags);
}
#endif
}
/**
* Returns whether the package is fully loaded.
*
* @return true if fully loaded or no file associated on disk, false otherwise
*/
bool UPackage::IsFullyLoaded() const
{
// Newly created packages aren't loaded and therefore haven't been marked as being fully loaded. They are treated as fully
// loaded packages though in this case, which is why we are looking to see whether the package exists on disk and assume it
// has been fully loaded if it doesn't.
if (!bHasBeenFullyLoaded && !HasAnyInternalFlags(EInternalObjectFlags::AsyncLoading) && FileSize == 0)
{
FString DummyFilename;
FString SourcePackageName = FileName != NAME_None ? FileName.ToString() : GetName();
// Try to find matching package in package file cache. We use the source package name here as it may be loaded into a temporary package
if (HasAnyPackageFlags(PKG_CompiledIn))
{
// Native packages don't have a file size but are always considered fully loaded.
bHasBeenFullyLoaded = true;
}
else if (!GetConvertedDynamicPackageNameToTypeName().Contains(GetFName()) &&
(!FPackageName::DoesPackageExist(*SourcePackageName, NULL, &DummyFilename) ||
(GIsEditor && IFileManager::Get().FileSize(*DummyFilename) < 0)))
{
// Package has NOT been found, so we assume it's a newly created one and therefore fully loaded.
bHasBeenFullyLoaded = true;
}
}
return bHasBeenFullyLoaded;
}
void UPackage::BeginDestroy()
{
// Detach linker if still attached
if (LinkerLoad)
{
// Detach() below will most likely null the LinkerLoad so keep a temp copy so that we can still call RemoveLinker on it
FLinkerLoad* LocalLinkerToRemove = LinkerLoad;
LocalLinkerToRemove->Detach();
FLinkerManager::Get().RemoveLinker(LocalLinkerToRemove);
LinkerLoad = nullptr;
}
Super::BeginDestroy();
}
bool UPackage::IsPostLoadThreadSafe() const
{
return true;
}
// UE-21181 - Tracking where the loaded editor level's package gets flagged as a PIE object
#if WITH_EDITOR
UPackage* UPackage::EditorPackage = nullptr;
void UPackage::SetPackageFlagsTo(uint32 NewFlags)
{
PackageFlagsPrivate = NewFlags;
ensure(((NewFlags & PKG_PlayInEditor) == 0) || (this != EditorPackage));
}
#endif
#if WITH_EDITORONLY_DATA
void FixupPackageEditorOnlyFlag(FName PackageThatGotEditorOnlyFlagCleared, bool bRecursive);
void UPackage::SetLoadedByEditorPropertiesOnly(bool bIsEditorOnly, bool bRecursive /*= false*/)
{
const bool bWasEditorOnly = bLoadedByEditorPropertiesOnly;
bLoadedByEditorPropertiesOnly = bIsEditorOnly;
if (bWasEditorOnly && !bIsEditorOnly)
{
FixupPackageEditorOnlyFlag(GetFName(), bRecursive);
}
}
#endif
#if WITH_EDITORONLY_DATA
IMPLEMENT_CORE_INTRINSIC_CLASS(UPackage, UObject,
{
Class->EmitObjectReference(STRUCT_OFFSET(UPackage, MetaData), TEXT("MetaData"));
});
#else
IMPLEMENT_CORE_INTRINSIC_CLASS(UPackage, UObject,
{});
#endif