// Copyright Epic Games, Inc. All Rights Reserved. #include "UObject/CoreNative.h" #include "Misc/CoreDelegates.h" #include "Modules/ModuleManager.h" #include "UObject/Class.h" #include "UObject/UnrealType.h" #include "Misc/PackageName.h" #include "Misc/RuntimeErrors.h" #include "UObject/Stack.h" void UClassRegisterAllCompiledInClasses(); bool IsInAsyncLoadingThreadCoreUObjectInternal(); bool IsAsyncLoadingCoreUObjectInternal(); void SuspendAsyncLoadingInternal(); void ResumeAsyncLoadingInternal(); bool IsAsyncLoadingSuspendedInternal(); bool IsAsyncLoadingMultithreadedCoreUObjectInternal(); // CoreUObject module. Handles UObject system pre-init (registers init function with Core callbacks). class FCoreUObjectModule: public FDefaultModuleImpl { public: static void RouteRuntimeMessageToBP(ELogVerbosity::Type Verbosity, const ANSICHAR* FileName, int32 LineNumber, const FText& Message) { #if UE_RAISE_RUNTIME_ERRORS && !NO_LOGGING check((Verbosity == ELogVerbosity::Error) || (Verbosity == ELogVerbosity::Warning)); FMsg::Logf_Internal(FileName, LineNumber, LogScript.GetCategoryName(), Verbosity, TEXT("%s(%d): Runtime %s: \"%s\""), ANSI_TO_TCHAR(FileName), LineNumber, (Verbosity == ELogVerbosity::Error) ? TEXT("Error") : TEXT("Warning"), *Message.ToString()); #endif FFrame::KismetExecutionMessage(*Message.ToString(), Verbosity); } virtual void StartupModule() override { // Register all classes that have been loaded so far. This is required for CVars to work. UClassRegisterAllCompiledInClasses(); void InitUObject(); FCoreDelegates::OnInit.AddStatic(InitUObject); // Substitute Core version of async loading functions with CoreUObject ones. IsInAsyncLoadingThread = &IsInAsyncLoadingThreadCoreUObjectInternal; IsAsyncLoading = &IsAsyncLoadingCoreUObjectInternal; SuspendAsyncLoading = &SuspendAsyncLoadingInternal; ResumeAsyncLoading = &ResumeAsyncLoadingInternal; IsAsyncLoadingSuspended = &IsAsyncLoadingSuspendedInternal; IsAsyncLoadingMultithreaded = &IsAsyncLoadingMultithreadedCoreUObjectInternal; // Register the script callstack callback to the runtime error logging #if UE_RAISE_RUNTIME_ERRORS FRuntimeErrors::OnRuntimeIssueLogged.BindStatic(&FCoreUObjectModule::RouteRuntimeMessageToBP); #endif // Make sure that additional content mount points can be registered after CoreUObject loads FPackageName::EnsureContentPathsAreRegistered(); #if DO_BLUEPRINT_GUARD FFrame::InitPrintScriptCallstack(); #endif } }; IMPLEMENT_MODULE(FCoreUObjectModule, CoreUObject); // if we are not using compiled in natives, we still need this as a base class for intrinsics #if !USE_COMPILED_IN_NATIVES IMPLEMENT_CLASS(UObject, 0); COREUOBJECT_API class UClass* Z_Construct_UClass_UObject(); UClass* Z_Construct_UClass_UObject() { static UClass* OuterClass = nullptr; if (!OuterClass) { OuterClass = UObject::StaticClass(); UObjectForceRegistration(OuterClass); UObjectBase::EmitBaseReferences(OuterClass); OuterClass->StaticLink(); } check(OuterClass->GetClass()); return OuterClass; } #endif /*----------------------------------------------------------------------------- FObjectInstancingGraph. -----------------------------------------------------------------------------*/ FObjectInstancingGraph::FObjectInstancingGraph(bool bDisableInstancing) : SourceRoot(nullptr), DestinationRoot(nullptr), bCreatingArchetype(false), bEnableSubobjectInstancing(!bDisableInstancing), bLoadingObject(false) { } FObjectInstancingGraph::FObjectInstancingGraph(UObject* DestinationSubobjectRoot) : SourceRoot(nullptr), DestinationRoot(nullptr), bCreatingArchetype(false), bEnableSubobjectInstancing(true), bLoadingObject(false) { SetDestinationRoot(DestinationSubobjectRoot); } void FObjectInstancingGraph::SetDestinationRoot(UObject* DestinationSubobjectRoot, UObject* InSourceRoot /*= nullptr*/) { DestinationRoot = DestinationSubobjectRoot; check(DestinationRoot); SourceRoot = InSourceRoot ? InSourceRoot : DestinationRoot->GetArchetype(); check(SourceRoot); // add the subobject roots to the Source -> Destination mapping SourceToDestinationMap.Add(SourceRoot, DestinationRoot); bCreatingArchetype = DestinationSubobjectRoot->HasAnyFlags(RF_ArchetypeObject); } UObject* FObjectInstancingGraph::GetDestinationObject(UObject* SourceObject) { check(SourceObject); return SourceToDestinationMap.FindRef(SourceObject); } UObject* FObjectInstancingGraph::GetInstancedSubobject(UObject* SourceSubobject, UObject* CurrentValue, UObject* CurrentObject, bool bDoNotCreateNewInstance, bool bAllowSelfReference) { checkSlow(SourceSubobject); UObject* InstancedSubobject = INVALID_OBJECT; if (SourceSubobject != nullptr && CurrentValue != nullptr && !CurrentValue->IsIn(CurrentObject)) { const bool bAllowedSelfReference = bAllowSelfReference && SourceSubobject == SourceRoot; bool bShouldInstance = bAllowedSelfReference || SourceSubobject->IsIn(SourceRoot); if (!bShouldInstance && CurrentValue->GetOuter() == CurrentObject->GetArchetype()) { // this code is intended to catch cases where SourceRoot contains subobjects assigned to instanced object properties, where the subobject's class // contains components, and the class of the subobject is outside of the inheritance hierarchy of the SourceRoot, for example, a weapon // class which contains UIObject subobject definitions in its defaultproperties, where the property referencing the UIObjects is marked instanced. bShouldInstance = true; // if this case is triggered, ensure that the CurrentValue of the component property is still pointing to the template component. check(SourceSubobject == CurrentValue); } if (bShouldInstance) { // search for the unique component instance that corresponds to this component template InstancedSubobject = GetDestinationObject(SourceSubobject); if (InstancedSubobject == nullptr) { if (bDoNotCreateNewInstance) { InstancedSubobject = INVALID_OBJECT; // leave it unchanged } else { // if the Outer for the component currently assigned to this property is the same as the object that we're instancing components for, // the component does not need to be instanced; otherwise, there are two possiblities: // 1. CurrentValue is a template and needs to be instanced // 2. CurrentValue is an instanced component, in which case it should already be in InstanceGraph, UNLESS the component was created // at runtime (editinline export properties, for example). If that is the case, CurrentValue will be an instance that is not linked // to the component template referenced by CurrentObject's archetype, and in this case, we also don't want to re-instance the component template const bool bIsRuntimeInstance = CurrentValue != SourceSubobject && CurrentValue->GetOuter() == CurrentObject; if (bIsRuntimeInstance) { InstancedSubobject = CurrentValue; } else { // If the component template is relevant in this context(client vs server vs editor), instance it. const bool bShouldLoadForClient = SourceSubobject->NeedsLoadForClient(); const bool bShouldLoadForServer = SourceSubobject->NeedsLoadForServer(); const bool bShouldLoadForEditor = (GIsEditor && (bShouldLoadForClient || !CurrentObject->RootPackageHasAnyFlags(PKG_PlayInEditor))); if (((GIsClient && bShouldLoadForClient) || (GIsServer && bShouldLoadForServer) || bShouldLoadForEditor)) { // this is the first time the instance corresponding to SourceSubobject has been requested // get the object instance corresponding to the source component's Outer - this is the object that // will be used as the Outer for the destination component UObject* SubobjectOuter = GetDestinationObject(SourceSubobject->GetOuter()); // In the event we're templated off a deep nested UObject hierarchy, with several links to objects nested in the object // graph, it's entirely possible that we'll encounter UObjects that we haven't yet discovered and instanced a copy of their // outer. In that case - we need to go ahead and instance that outer. if (SubobjectOuter == nullptr) { SubobjectOuter = GetInstancedSubobject(SourceSubobject->GetOuter(), SourceSubobject->GetOuter(), CurrentObject, bDoNotCreateNewInstance, bAllowSelfReference); checkf(SubobjectOuter && SubobjectOuter != INVALID_OBJECT, TEXT("No corresponding destination object found for '%s' while attempting to instance component '%s'"), *SourceSubobject->GetOuter()->GetFullName(), *SourceSubobject->GetFullName()); } FName SubobjectName = SourceSubobject->GetFName(); // final archetype archetype will be the archetype of the template UObject* FinalSubobjectArchetype = CurrentValue->GetArchetype(); // Don't search for the existing subobjects on Blueprint-generated classes. What we'll find is a subobject // created by the constructor which may not have all of its fields initialized to the correct value (which // should be coming from a blueprint). // NOTE: Since this function is called ONLY for Blueprint-generated classes, we may as well delete this 'if'. if (!SubobjectOuter->GetClass()->HasAnyClassFlags(CLASS_CompiledFromBlueprint)) { InstancedSubobject = StaticFindObjectFast(nullptr, SubobjectOuter, SubobjectName); } if (InstancedSubobject && IsCreatingArchetype()) { // since we are updating an archetype, this needs to reconstruct as that is the mechanism used to copy properties // it will destroy the existing object and overwrite it InstancedSubobject = nullptr; } if (!InstancedSubobject) { // finally, create the component instance FStaticConstructObjectParameters Params(SourceSubobject->GetClass()); Params.Outer = SubobjectOuter; Params.Name = SubobjectName; Params.SetFlags = SubobjectOuter->GetMaskedFlags(RF_PropagateToSubObjects); Params.Template = SourceSubobject; Params.bCopyTransientsFromClassDefaults = true; Params.InstanceGraph = this; InstancedSubobject = StaticConstructObject_Internal(Params); } } } } } else if (IsLoadingObject() && InstancedSubobject->GetClass()->HasAnyClassFlags(CLASS_HasInstancedReference)) { /* When loading an object from disk, in some cases we have a component which has a reference to another component in DestinationObject which wasn't serialized and hasn't yet been instanced. For example, the PointLight class declared two component templates: Begin DrawLightRadiusComponent0 End Components.Add(DrawLightRadiusComponent0) Begin MyPointLightComponent SomeProperty=DrawLightRadiusComponent End LightComponent=MyPointLightComponent The components array will be processed by UClass::InstanceSubobjectTemplates after the LightComponent property is processed. If the instance of DrawLightRadiusComponent0 that was created during the last session (i.e. when this object was saved) was identical to the component template from the PointLight class's defaultproperties, and the instance of MyPointLightComponent was serialized, then the MyPointLightComponent instance will exist in the InstanceGraph, but the instance of DrawLightRadiusComponent0 will not. To handle this case and make sure that the SomeProperty variable of the MyPointLightComponent instance is correctly set to the value of the DrawLightRadiusComponent0 instance that will be created as a result of calling InstanceSubobjectTemplates on the PointLight actor from ConditionalPostLoad, we must call ConditionalPostLoad on each existing component instance that we encounter, while we still have access to all of the component instances owned by the PointLight. */ InstancedSubobject->ConditionalPostLoadSubobjects(this); } } } return InstancedSubobject; } UObject* FObjectInstancingGraph::InstancePropertyValue(class UObject* ComponentTemplate, class UObject* CurrentValue, class UObject* Owner, bool bIsTransient, bool bCausesInstancing, bool bAllowSelfReference) { UObject* NewValue = CurrentValue; check(CurrentValue); if (CurrentValue->GetClass()->HasAnyClassFlags(CLASS_DefaultToInstanced)) { bCausesInstancing = true; // these are always instanced no matter what } if (!IsSubobjectInstancingEnabled() || // if instancing is off (!bCausesInstancing && // or if this class isn't forced to be instanced and this var did not have the instance keyword !bAllowSelfReference)) // and this isn't a delegate { return NewValue; // not instancing } // if the object we're instancing the components for (Owner) has the current component's outer in its archetype chain, and its archetype has a nullptr value // for this component property it means that the archetype didn't instance its component, so we shouldn't either. if (ComponentTemplate == nullptr && CurrentValue != nullptr && (Owner && Owner->IsBasedOnArchetype(CurrentValue->GetOuter()))) { NewValue = nullptr; } else { if (ComponentTemplate == nullptr) { // should only be here if our archetype doesn't contain this component property ComponentTemplate = CurrentValue; } UObject* MaybeNewValue = GetInstancedSubobject(ComponentTemplate, CurrentValue, Owner, bAllowSelfReference, bAllowSelfReference); if (MaybeNewValue != INVALID_OBJECT) { NewValue = MaybeNewValue; } } return NewValue; } void FObjectInstancingGraph::AddNewObject(class UObject* ObjectInstance, UObject* InArchetype /*= nullptr*/) { check(!GEventDrivenLoaderEnabled || !InArchetype || !InArchetype->HasAnyFlags(RF_NeedLoad)); if (HasDestinationRoot()) { AddNewInstance(ObjectInstance, InArchetype); } else { SetDestinationRoot(ObjectInstance, InArchetype); } } void FObjectInstancingGraph::AddNewInstance(UObject* ObjectInstance, UObject* InArchetype /*= nullptr*/) { check(SourceRoot); check(DestinationRoot); if (ObjectInstance != nullptr) { UObject* SourceObject = InArchetype ? InArchetype : ObjectInstance->GetArchetype(); check(SourceObject); SourceToDestinationMap.Add(SourceObject, ObjectInstance); } } void FObjectInstancingGraph::RetrieveObjectInstances(UObject* SearchOuter, TArray& out_Objects) { if (HasDestinationRoot() && SearchOuter != nullptr && (SearchOuter == DestinationRoot || SearchOuter->IsIn(DestinationRoot))) { for (TMap::TIterator It(SourceToDestinationMap); It; ++It) { UObject* InstancedObject = It.Value(); if (InstancedObject->GetOuter() == SearchOuter) { out_Objects.AddUnique(InstancedObject); } } } }