// Copyright Epic Games, Inc. All Rights Reserved. #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" #include "UObject/Class.h" #include "UObject/UnrealType.h" #include "UObject/UnrealTypePrivate.h" #include "UObject/PropertyHelper.h" #include "UObject/LinkerPlaceholderFunction.h" #include "Serialization/ArchiveUObjectFromStructuredArchive.h" // WARNING: This should always be the last include in any file that needs it (except .generated.h) #include "UObject/UndefineUPropertyMacros.h" FMulticastScriptDelegate::FInvocationList FMulticastDelegateProperty::EmptyList; #if WITH_EDITORONLY_DATA FMulticastDelegateProperty::FMulticastDelegateProperty(UField* InField) : FProperty(InField) { UMulticastDelegateProperty* SourceProperty = CastChecked(InField); SignatureFunction = SourceProperty->SignatureFunction; } #endif // WITH_EDITORONLY_DATA void FMulticastDelegateProperty::PostDuplicate(const FField& InField) { const FMulticastDelegateProperty& Source = static_cast(InField); SignatureFunction = Source.SignatureFunction; Super::PostDuplicate(InField); } /*----------------------------------------------------------------------------- FMulticastDelegateProperty. -----------------------------------------------------------------------------*/ void FMulticastDelegateProperty::InstanceSubobjects(void* Data, void const* DefaultData, UObject* InOwner, FObjectInstancingGraph* InstanceGraph) { if (DefaultData) { for (int32 i = 0; i < ArrayDim; i++) { // Fix up references to the class default object (if necessary) FMulticastScriptDelegate::FInvocationList::TIterator CurInvocation(GetInvocationList((uint8*)Data + i)); FMulticastScriptDelegate::FInvocationList::TIterator DefaultInvocation(GetInvocationList((uint8*)DefaultData + i)); for (; CurInvocation && DefaultInvocation; ++CurInvocation, ++DefaultInvocation) { FScriptDelegate& DestDelegateInvocation = *CurInvocation; UObject* CurrentUObject = DestDelegateInvocation.GetUObject(); if (CurrentUObject) { FScriptDelegate& DefaultDelegateInvocation = *DefaultInvocation; UObject* Template = DefaultDelegateInvocation.GetUObject(); UObject* NewUObject = InstanceGraph->InstancePropertyValue(Template, CurrentUObject, InOwner, HasAnyPropertyFlags(CPF_Transient), false, true); DestDelegateInvocation.BindUFunction(NewUObject, DestDelegateInvocation.GetFunctionName()); } } // now finish up the ones for which there is no default for (; CurInvocation; ++CurInvocation) { FScriptDelegate& DestDelegateInvocation = *CurInvocation; UObject* CurrentUObject = DestDelegateInvocation.GetUObject(); if (CurrentUObject) { UObject* NewUObject = InstanceGraph->InstancePropertyValue(NULL, CurrentUObject, InOwner, HasAnyPropertyFlags(CPF_Transient), false, true); DestDelegateInvocation.BindUFunction(NewUObject, DestDelegateInvocation.GetFunctionName()); } } } } else // no default data { for (int32 i = 0; i < ArrayDim; i++) { for (FMulticastScriptDelegate::FInvocationList::TIterator CurInvocation(GetInvocationList((uint8*)Data + i)); CurInvocation; ++CurInvocation) { FScriptDelegate& DestDelegateInvocation = *CurInvocation; UObject* CurrentUObject = DestDelegateInvocation.GetUObject(); if (CurrentUObject) { UObject* NewUObject = InstanceGraph->InstancePropertyValue(NULL, CurrentUObject, InOwner, HasAnyPropertyFlags(CPF_Transient), false, true); DestDelegateInvocation.BindUFunction(NewUObject, DestDelegateInvocation.GetFunctionName()); } } } } } bool FMulticastDelegateProperty::Identical(const void* A, const void* B, uint32 PortFlags) const { const FMulticastScriptDelegate::FInvocationList& ListA = GetInvocationList(A); const FMulticastScriptDelegate::FInvocationList& ListB = GetInvocationList(B); const int32 ListASize = ListA.Num(); if (ListASize != ListB.Num()) { return false; } for (int32 CurInvocationIndex = 0; CurInvocationIndex != ListASize; ++CurInvocationIndex) { const FScriptDelegate& BindingA = ListA[CurInvocationIndex]; const FScriptDelegate& BindingB = ListB[CurInvocationIndex]; if (BindingA.GetUObject() != BindingB.GetUObject()) { return false; } if (BindingA.GetFunctionName() != BindingB.GetFunctionName()) { return false; } } return true; } bool FMulticastDelegateProperty::NetSerializeItem(FArchive& Ar, UPackageMap* Map, void* Data, TArray* MetaData) const { // Do not allow replication of delegates, as there is no way to make this secure (it allows the execution of any function in any object, on the remote client/server) return 1; } FString FMulticastDelegateProperty::GetCPPType(FString* ExtendedTypeText /*=NULL*/, uint32 CPPExportFlags /*=0*/) const { #if HACK_HEADER_GENERATOR // We have this test because sometimes the delegate hasn't been set up by FixupDelegateProperties at the time // we need the type for an error message. We deliberately format it so that it's unambiguously not CPP code, but is still human-readable. if (!SignatureFunction) { return FString(TEXT("{multicast delegate type}")); } #endif FString UnmangledFunctionName = SignatureFunction->GetName().LeftChop(FString(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX).Len()); const UClass* OwnerClass = SignatureFunction->GetOwnerClass(); const bool bBlueprintCppBackend = (0 != (CPPExportFlags & EPropertyExportCPPFlags::CPPF_BlueprintCppBackend)); const bool bNative = SignatureFunction->IsNative(); if (bBlueprintCppBackend && bNative) { UStruct* StructOwner = Cast(SignatureFunction->GetOuter()); if (StructOwner) { return FString::Printf(TEXT("%s%s::F%s"), StructOwner->GetPrefixCPP(), *StructOwner->GetName(), *UnmangledFunctionName); } } else { if ((0 != (CPPExportFlags & EPropertyExportCPPFlags::CPPF_BlueprintCppBackend)) && OwnerClass && !OwnerClass->HasAnyClassFlags(CLASS_Native)) { // The name must be valid, this removes spaces, ?, etc from the user's function name. It could // be slightly shorter because the postfix ("__pf") is not needed here because we further post- // pend to the string. Normally the postfix is needed to make sure we don't mangle to a valid // identifier and collide: UnmangledFunctionName = UnicodeToCPPIdentifier(UnmangledFunctionName, false, TEXT("")); // the name must be unique const FString OwnerName = UnicodeToCPPIdentifier(OwnerClass->GetName(), false, TEXT("")); const FString NewUnmangledFunctionName = FString::Printf(TEXT("%s__%s"), *UnmangledFunctionName, *OwnerName); UnmangledFunctionName = NewUnmangledFunctionName; } if (0 != (CPPExportFlags & EPropertyExportCPPFlags::CPPF_CustomTypeName)) { UnmangledFunctionName += TEXT("__MulticastDelegate"); } } return FString(TEXT("F")) + UnmangledFunctionName; } FString FMulticastDelegateProperty::GetCPPTypeForwardDeclaration() const { return FString(); } void FMulticastDelegateProperty::ExportTextItem(FString& ValueStr, const void* PropertyValue, const void* DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const { if (0 != (PortFlags & PPF_ExportCpp)) { ValueStr += TEXT("{}"); return; } const FMulticastScriptDelegate::FInvocationList& InvocationList = GetInvocationList(PropertyValue); // Start delegate array with open paren ValueStr += TEXT("("); bool bIsFirstFunction = true; for (FMulticastScriptDelegate::FInvocationList::TConstIterator CurInvocation(InvocationList); CurInvocation; ++CurInvocation) { if (CurInvocation->IsBound()) { if (!bIsFirstFunction) { ValueStr += TEXT(","); } bIsFirstFunction = false; bool bDelegateHasValue = CurInvocation->GetFunctionName() != NAME_None; ValueStr += FString::Printf(TEXT("%s.%s"), CurInvocation->GetUObject() != NULL ? *CurInvocation->GetUObject()->GetName() : TEXT("(null)"), *CurInvocation->GetFunctionName().ToString()); } } // Close the array (NOTE: It could be empty, but that's fine.) ValueStr += TEXT(")"); } const TCHAR* FMulticastDelegateProperty::ImportDelegateFromText(FMulticastScriptDelegate& MulticastDelegate, const TCHAR* Buffer, UObject* Parent, FOutputDevice* ErrorText) const { // Multi-cast delegates always expect an opening parenthesis when using assignment syntax, so that // users don't accidentally blow away already-bound delegates in DefaultProperties. This also helps // to differentiate between single-cast and multi-cast delegates if (*Buffer != TCHAR('(')) { return NULL; } // Clear the existing delegate MulticastDelegate.Clear(); // process opening parenthesis ++Buffer; SkipWhitespace(Buffer); // Empty Multi-cast delegates is still valid. if (*Buffer == TCHAR(')')) { return Buffer; } do { // Parse the delegate FScriptDelegate ImportedDelegate; Buffer = DelegatePropertyTools::ImportDelegateFromText(ImportedDelegate, SignatureFunction, Buffer, Parent, ErrorText); if (Buffer == NULL) { return NULL; } // Add this delegate to our multicast delegate's invocation list MulticastDelegate.AddUnique(ImportedDelegate); SkipWhitespace(Buffer); } while (*Buffer == TCHAR(',') && Buffer++); // We expect a closing paren if (*(Buffer++) != TCHAR(')')) { return NULL; } return MulticastDelegate.IsBound() ? Buffer : NULL; } const TCHAR* FMulticastDelegateProperty::ImportText_Add(const TCHAR* Buffer, void* PropertyValue, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText) const { if (!ValidateImportFlags(PortFlags, ErrorText)) { return NULL; } // Parse the delegate FScriptDelegate ImportedDelegate; Buffer = DelegatePropertyTools::ImportDelegateFromText(ImportedDelegate, SignatureFunction, Buffer, Parent, ErrorText); if (Buffer == NULL) { return NULL; } // Add this delegate to our multicast delegate's invocation list AddDelegate(MoveTemp(ImportedDelegate), Parent, PropertyValue); SkipWhitespace(Buffer); return Buffer; } const TCHAR* FMulticastDelegateProperty::ImportText_Remove(const TCHAR* Buffer, void* PropertyValue, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText) const { if (!ValidateImportFlags(PortFlags, ErrorText)) { return NULL; } // Parse the delegate FScriptDelegate ImportedDelegate; Buffer = DelegatePropertyTools::ImportDelegateFromText(ImportedDelegate, SignatureFunction, Buffer, Parent, ErrorText); if (Buffer == NULL) { return NULL; } // Remove this delegate from our multicast delegate's invocation list RemoveDelegate(ImportedDelegate, Parent, PropertyValue); SkipWhitespace(Buffer); return Buffer; } void FMulticastDelegateProperty::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar << SignatureFunction; #if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING if (Ar.IsLoading() || Ar.IsObjectReferenceCollector()) { if (auto PlaceholderFunc = Cast(SignatureFunction)) { PlaceholderFunc->AddReferencingProperty(this); } } #endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING } void FMulticastDelegateProperty::BeginDestroy() { #if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING if (auto PlaceholderFunc = Cast(SignatureFunction)) { PlaceholderFunc->RemoveReferencingProperty(this); } #endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING Super::BeginDestroy(); } bool FMulticastDelegateProperty::SameType(const FProperty* Other) const { return Super::SameType(Other) && (SignatureFunction == ((FMulticastDelegateProperty*)Other)->SignatureFunction); } EConvertFromTypeResult FMulticastDelegateProperty::ConvertFromType(const FPropertyTag& Tag, FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct) { // Multicast delegate properties are serialization compatible if (Tag.Type == NAME_MulticastDelegateProperty || Tag.Type == FMulticastInlineDelegateProperty::StaticClass()->GetFName() || Tag.Type == FMulticastSparseDelegateProperty::StaticClass()->GetFName()) { uint8* DestAddress = ContainerPtrToValuePtr(Data, Tag.ArrayIndex); SerializeItem(Slot, DestAddress, nullptr); return EConvertFromTypeResult::Converted; } return EConvertFromTypeResult::UseSerializeItem; } void FMulticastDelegateProperty::AddReferencedObjects(FReferenceCollector& Collector) { #if USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING if (SignatureFunction && !SignatureFunction->IsA()) #endif // USE_CIRCULAR_DEPENDENCY_LOAD_DEFERRING { Collector.AddReferencedObject(SignatureFunction); } Super::AddReferencedObjects(Collector); } IMPLEMENT_FIELD(FMulticastDelegateProperty) const FMulticastScriptDelegate* FMulticastInlineDelegateProperty::GetMulticastDelegate(const void* PropertyValue) const { return (const FMulticastScriptDelegate*)PropertyValue; } void FMulticastInlineDelegateProperty::SetMulticastDelegate(void* PropertyValue, FMulticastScriptDelegate ScriptDelegate) const { *(FMulticastScriptDelegate*)PropertyValue = MoveTemp(ScriptDelegate); } FMulticastScriptDelegate::FInvocationList& FMulticastInlineDelegateProperty::GetInvocationList(const void* PropertyValue) const { return (PropertyValue ? ((FMulticastScriptDelegate*)PropertyValue)->InvocationList : EmptyList); } void FMulticastInlineDelegateProperty::SerializeItem(FStructuredArchive::FSlot Slot, void* Value, void const* Defaults) const { FArchiveUObjectFromStructuredArchive Adapter(Slot); FArchive& Ar = Adapter.GetArchive(); Ar << *GetPropertyValuePtr(Value); } const TCHAR* FMulticastInlineDelegateProperty::ImportText_Internal(const TCHAR* Buffer, void* PropertyValue, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText) const { FMulticastScriptDelegate& MulticastDelegate = (*(FMulticastScriptDelegate*)PropertyValue); return ImportDelegateFromText(MulticastDelegate, Buffer, Parent, ErrorText); } void ResolveDelegateReference(const FMulticastInlineDelegateProperty* InlineProperty, UObject*& Parent, void*& PropertyValue) { if (PropertyValue == nullptr) { checkf(Parent, TEXT("Must specify at least one of Parent or PropertyValue")); PropertyValue = InlineProperty->GetPropertyValuePtr_InContainer(Parent); } // Owner doesn't matter for inline delegates, so we don't worry about the Owner == nullptr case } void FMulticastInlineDelegateProperty::AddDelegate(FScriptDelegate ScriptDelegate, UObject* Parent, void* PropertyValue) const { ResolveDelegateReference(this, Parent, PropertyValue); FMulticastScriptDelegate& MulticastDelegate = (*(FMulticastScriptDelegate*)PropertyValue); // Add this delegate to our multicast delegate's invocation list MulticastDelegate.AddUnique(MoveTemp(ScriptDelegate)); } void FMulticastInlineDelegateProperty::RemoveDelegate(const FScriptDelegate& ScriptDelegate, UObject* Parent, void* PropertyValue) const { ResolveDelegateReference(this, Parent, PropertyValue); FMulticastScriptDelegate& MulticastDelegate = (*(FMulticastScriptDelegate*)PropertyValue); // Remove this delegate from our multicast delegate's invocation list MulticastDelegate.Remove(ScriptDelegate); } void FMulticastInlineDelegateProperty::ClearDelegate(UObject* Parent, void* PropertyValue) const { ResolveDelegateReference(this, Parent, PropertyValue); FMulticastScriptDelegate& MulticastDelegate = (*(FMulticastScriptDelegate*)PropertyValue); MulticastDelegate.Clear(); } IMPLEMENT_FIELD(FMulticastInlineDelegateProperty) const FMulticastScriptDelegate* FMulticastSparseDelegateProperty::GetMulticastDelegate(const void* PropertyValue) const { const FSparseDelegate* SparseDelegate = (const FSparseDelegate*)PropertyValue; if (SparseDelegate->IsBound()) { USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); UObject* OwningObject = FSparseDelegateStorage::ResolveSparseOwner(*SparseDelegate, SparseDelegateFunc->OwningClassName, SparseDelegateFunc->DelegateName); return FSparseDelegateStorage::GetMulticastDelegate(OwningObject, SparseDelegateFunc->DelegateName); } return nullptr; } void FMulticastSparseDelegateProperty::SetMulticastDelegate(void* PropertyValue, FMulticastScriptDelegate ScriptDelegate) const { FSparseDelegate& SparseDelegate = *(FSparseDelegate*)PropertyValue; USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); UObject* OwningObject = FSparseDelegateStorage::ResolveSparseOwner(SparseDelegate, SparseDelegateFunc->OwningClassName, SparseDelegateFunc->DelegateName); if (ScriptDelegate.IsBound()) { FSparseDelegateStorage::SetMulticastDelegate(OwningObject, SparseDelegateFunc->DelegateName, MoveTemp(ScriptDelegate)); SparseDelegate.bIsBound = true; } else if (SparseDelegate.bIsBound) { FSparseDelegateStorage::Clear(OwningObject, SparseDelegateFunc->DelegateName); SparseDelegate.bIsBound = false; } } FMulticastScriptDelegate::FInvocationList& FMulticastSparseDelegateProperty::GetInvocationList(const void* PropertyValue) const { if (FSparseDelegate* SparseDelegate = (FSparseDelegate*)PropertyValue) { if (SparseDelegate->IsBound()) { USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); UObject* OwningObject = FSparseDelegateStorage::ResolveSparseOwner(*SparseDelegate, SparseDelegateFunc->OwningClassName, SparseDelegateFunc->DelegateName); if (FMulticastScriptDelegate* Delegate = FSparseDelegateStorage::GetMulticastDelegate(OwningObject, SparseDelegateFunc->DelegateName)) { return Delegate->InvocationList; } } } return EmptyList; } void FMulticastSparseDelegateProperty::SerializeItem(FStructuredArchive::FSlot Slot, void* Value, void const* Defaults) const { FArchiveUObjectFromStructuredArchive Adapter(Slot); SerializeItemInternal(Adapter.GetArchive(), Value, Defaults); } void FMulticastSparseDelegateProperty::SerializeItemInternal(FArchive& Ar, void* Value, void const* Defaults) const { FSparseDelegate& SparseDelegate = *(FSparseDelegate*)Value; if (Ar.IsLoading()) { FMulticastScriptDelegate Delegate; Ar << Delegate; if (Delegate.IsBound()) { USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); UObject* OwningObject = FSparseDelegateStorage::ResolveSparseOwner(SparseDelegate, SparseDelegateFunc->OwningClassName, SparseDelegateFunc->DelegateName); FSparseDelegateStorage::SetMulticastDelegate(OwningObject, SparseDelegateFunc->DelegateName, MoveTemp(Delegate)); SparseDelegate.bIsBound = true; } else if (SparseDelegate.bIsBound) { USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); UObject* OwningObject = FSparseDelegateStorage::ResolveSparseOwner(SparseDelegate, SparseDelegateFunc->OwningClassName, SparseDelegateFunc->DelegateName); FSparseDelegateStorage::Clear(OwningObject, SparseDelegateFunc->DelegateName); SparseDelegate.bIsBound = false; } } else { if (SparseDelegate.IsBound()) { USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); UObject* OwningObject = FSparseDelegateStorage::ResolveSparseOwner(SparseDelegate, SparseDelegateFunc->OwningClassName, SparseDelegateFunc->DelegateName); if (FMulticastScriptDelegate* Delegate = FSparseDelegateStorage::GetMulticastDelegate(OwningObject, SparseDelegateFunc->DelegateName)) { Ar << *Delegate; } else { Ar << EmptyList; } } else { Ar << EmptyList; } } } const TCHAR* FMulticastSparseDelegateProperty::ImportText_Internal(const TCHAR* Buffer, void* PropertyValue, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText) const { FMulticastScriptDelegate Delegate; const TCHAR* Result = ImportDelegateFromText(Delegate, Buffer, Parent, ErrorText); if (Result) { FSparseDelegate& SparseDelegate = *(FSparseDelegate*)PropertyValue; USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); if (Delegate.IsBound()) { FSparseDelegateStorage::SetMulticastDelegate(Parent, SparseDelegateFunc->DelegateName, MoveTemp(Delegate)); SparseDelegate.bIsBound = true; } else { FSparseDelegateStorage::Clear(Parent, SparseDelegateFunc->DelegateName); SparseDelegate.bIsBound = false; } } return Result; } void ResolveDelegateReference(const FMulticastSparseDelegateProperty* SparseProperty, UObject*& Parent, void*& PropertyValue) { USparseDelegateFunction* SparseDelegateFunc = CastChecked(SparseProperty->SignatureFunction); if (Parent == nullptr) { checkf(PropertyValue, TEXT("Must specify at least one of Parent or PropertyValue")); Parent = FSparseDelegateStorage::ResolveSparseOwner(*(FSparseDelegate*)PropertyValue, SparseDelegateFunc->OwningClassName, SparseDelegateFunc->DelegateName); } else if (PropertyValue) { checkSlow(Parent == FSparseDelegateStorage::ResolveSparseOwner(*(FSparseDelegate*)PropertyValue, SparseDelegateFunc->OwningClassName, SparseDelegateFunc->DelegateName)); } else { PropertyValue = SparseProperty->GetPropertyValuePtr_InContainer(Parent); } } void FMulticastSparseDelegateProperty::AddDelegate(FScriptDelegate ScriptDelegate, UObject* Parent, void* PropertyValue) const { ResolveDelegateReference(this, Parent, PropertyValue); USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); FSparseDelegate& SparseDelegate = *(FSparseDelegate*)PropertyValue; SparseDelegate.__Internal_AddUnique(Parent, SparseDelegateFunc->DelegateName, MoveTemp(ScriptDelegate)); } void FMulticastSparseDelegateProperty::RemoveDelegate(const FScriptDelegate& ScriptDelegate, UObject* Parent, void* PropertyValue) const { ResolveDelegateReference(this, Parent, PropertyValue); USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); FSparseDelegate& SparseDelegate = *(FSparseDelegate*)PropertyValue; SparseDelegate.__Internal_Remove(Parent, SparseDelegateFunc->DelegateName, ScriptDelegate); } void FMulticastSparseDelegateProperty::ClearDelegate(UObject* Parent, void* PropertyValue) const { ResolveDelegateReference(this, Parent, PropertyValue); USparseDelegateFunction* SparseDelegateFunc = CastChecked(SignatureFunction); FSparseDelegate& SparseDelegate = *(FSparseDelegate*)PropertyValue; SparseDelegate.__Internal_Clear(Parent, SparseDelegateFunc->DelegateName); } IMPLEMENT_FIELD(FMulticastSparseDelegateProperty) #include "UObject/DefineUPropertyMacros.h"