Context
In blueprint, one can bind to a referenced actor’s events by clicking an actor reference variable, and creating an event node through the variable’s detail panel. Under the hood, this adds an entry to the BlueprintGeneratedClass’s DynamicBindingObjects, which sets up the binding during AActor::ExecuteConstruction by modifying the referenced actor’s FMulticastInlineDelegateProperty (Event Dispatcher) value.
[Image Removed]
Problem
The referenced actor can end up in an invalid state, when the referencing and the referenced actors aren’t saved in tandem on a map with OFPA enabled. For example, clearing the reference but not resaving the referenced actor results in the cleared binding being reloaded. To make matters worse, the referenced actor is not marked dirty despite its Event Dispatcher state being changed.
If BP_ActorB binds to BP_ActorA’s event dispatcher, that binding is stored in BP_ActorA’s state. If BP_ActorB clears the reference to BP_ActorA, that binding is cleaned up and BP_ActorB is dirtied. However, BP_ActorA is not marked as dirty despite BP_ActorB having made a modification to A’s state. When reloading the map, BP_ActorA will still have BP_ActorB in its callback list.
Alternatively, if BP_ActorB’s event node is deleted, BP_ActorA still keeps an entry in its event dispatcher’s invocation list, despite that the function no longer exists. This will never get cleaned up, except if BP_ActorB is deleted from the map.
The following snippet can be used during debugging to print out the number of entries in BP_ActorA’s event dispatcher’s invocation list (assuming BP_ActorA’s event dispatcher is called ‘NewEventDispatcher’):
void AMyActor::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
PrintOutNumDelegateBindings("OnConstruction");
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
PrintOutNumDelegateBindings("BeginPlay");
}
void AMyActor::PrintOutNumDelegateBindings(const FString& Prefix)
{
if (FMulticastInlineDelegateProperty* Prop = (FMulticastInlineDelegateProperty*)GetClass()->FindPropertyByName(FName("NewEventDispatcher")))
{
FMulticastScriptDelegate* ScriptDelegate = nullptr;
ScriptDelegate = Prop->GetPropertyValuePtr_InContainer(this);
const int32 NumObjects = ScriptDelegate->GetAllObjects().Num();
UE_LOG(LogTemp, Warning, TEXT("%s | Delegates num: %d"), *Prefix, NumObjects);
}
}
Suggested Fix
At the very least, BP_ActorA should be marked dirty when BP_ActorB sets or clears a reference to BP_ActorA and it has an event node that auto-binds to its event dispatcher. Ideally, BP_ActorA’s event dispatcher entries are checked on load and unintentional callbacks are cleaned up.
There are multiple ways to reproduce dangling bindings.
Shared Setup
Repro 1:
Repro 2:
Non-fatal callstack. Just attaching this as a reference: this is an example where recompiling one actor class modifies the referenced actor’s Event Dispatcher (FMulticastDelegateProperty).
> UnrealEditor-Engine.dll!UComponentDelegateBinding::BindDynamicDelegates(UObject * InInstance) Line 32 C++ UnrealEditor-Engine.dll!UBlueprintGeneratedClass::BindDynamicDelegates(const UClass * ThisClass, UObject * InInstance) Line 1621 C++ UnrealEditor-Engine.dll!AActor::ExecuteConstruction(const UE::Math::TTransform<double> & Transform, const FRotationConversionCache * TransformRotationCache, const FComponentInstanceDataCache * InstanceDataCache, bool bIsDefaultTransform, ESpawnActorScaleMethod TransformScaleMethod) Line 958 C++ UnrealEditor-UnrealEd.dll!FActorReplacementHelper::Finalize(const TMap<UObject *,UObject *,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<UObject *,UObject *,0>> & OldToNewInstanceMap, const TSet<UObject *,DefaultKeyFuncs<UObject *,0>,FDefaultSetAllocator> * ObjectsThatShouldUseOldStuff, const TArray<UObject *,TSizedDefaultAllocator<32>> & ObjectsToReplace, const TMap<FSoftObjectPath,UObject *,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<FSoftObjectPath,UObject *,0>> & ReinstancedObjectsWeakReferenceMap) Line 1563 C++ UnrealEditor-UnrealEd.dll!FBlueprintCompileReinstancer::ReplaceInstancesOfClass_Inner(const TMap<UClass *,UClass *,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<UClass *,UClass *,0>> & InOldToNewClassMap, const FReplaceInstancesOfClassParameters & Params) Line 2962 C++ UnrealEditor-UnrealEd.dll!FBlueprintCompileReinstancer::BatchReplaceInstancesOfClass(const TMap<UClass *,UClass *,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<UClass *,UClass *,0>> & InOldToNewClassMap, const FReplaceInstancesOfClassParameters & Params) Line 1942 C++ UnrealEditor-Kismet.dll!FBlueprintCompilationManagerImpl::FlushReinstancingQueueImpl(bool bFindAndReplaceCDOReferences, TMap<UClass *,TMap<UObject *,UObject *,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<UObject *,UObject *,0>>,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<UClass *,TMap<UObject *,UObject *,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<UObject *,UObject *,0>>,0>> * OldToNewTemplates) Line 2067 C++ UnrealEditor-Kismet.dll!FBlueprintCompilationManagerImpl::CompileSynchronouslyImpl(const FBPCompileRequest & Request) Line 380 C++ UnrealEditor-UnrealEd.dll!FKismetEditorUtilities::CompileBlueprint(UBlueprint * BlueprintObj, EBlueprintCompileOptions CompileFlags, FCompilerResultsLog * pResults) Line 801 C++ UnrealEditor-Kismet.dll!FBlueprintEditor::Compile() Line 4235 C++
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-366632 in the post.
| 0 |
| Component | UE - Framework - Blueprint Editor |
|---|---|
| Affects Versions | 5.7.4 |
| Created | Feb 19, 2026 |
|---|---|
| Updated | Feb 19, 2026 |