Following the repro steps, when assigning an actor instance (A) that contains a multicast delegate to the property of another actor instance (B), instance A has its multicast delegate property updated to have a callback function that is referenced on instance B. If you then save instance A without saving instance B, instance A has an object reference to instance B without instance B knowing about it. I’ve debugged this and the function that is making the change is UComponentDelegateBinding::BindDynamicDelegates.
I believe that this issue has led to some of our actor instances having hidden references to other actors that are no longer referencing the actor with the multicast delegate. It seems to cause some of our actors to fail validation when we try to delete them because of this hidden reference. We have verified this as we can run an editor utility widget function to unbind all events from the delegate during edit time which allows us get past validation and delete the actor.
The issue can be avoided if we bind an event at runtime through BeginPlay on the actor but when the binding is automatically created through the Events section using the details panel, the issue can be introduced. Are there any suggestions on a way we can clean up these references that no longer need to be there?
Create a new Blueprint actor (BP_DelegateActor) and add a delegate to it under the Event Dispatchers section in MyBlueprint.
In BeginPlay of BP_DelegateActor, add a Call node for the delegate you created and connect it to the execution path.
Create another Blueprint actor (BP_ActorReferencer) and add a variable of object type BP_DelegateActor and expose it to be instance editable.
Select the new object property in the BP_ActorReferencer blueprint class and click the + button on the delegate property under the Events section in the details panel. Add a print string node call off of this callback.
Open a map that is world partitioned, add an instance of BP_DelegateActor and BP_ActorReferencer to the world, and save these instances.
On the BP_ActorReferencer instance, find the object type BP_DelegateActor property and assign the instance of BP_DelegateActor to that property.
Select the instance of BP_ActorReferencer and move the actor slightly to dirty the package and then save only this package.
Reload the same level in editor without saving the instance of BP_DelegateActor. You will see that the BP_ActorReferencer instance doesn’t reference the BP_DelegateActor instance anymore because it wasn’t saved (it should display None).
Start a PIE session and see that the print node inside BP_ActorReferencer is still executed on the instance that exists in the level even though it has nothing assigned to the BP_DelegateActor object type property.
[Attachment Removed]
Hello! We’re aware of some oddities around blueprint Event Dispatchers and other blueprints binding to it via Events in the details panel. UE-273286 is a closely related bug that affects UE 5.5 and 5.6, but I fixed for 5.7. I suspect both the workaround and the fix for that bug will also help your case.
The fix is CL 44382795 on UE5 main, or click that link for the github commit. That should clean up the hidden references from A (delegate) -> B (referencer) the next time that blueprint B is recompiled:
Either when you open the map containing A and B,
or if you manually compile B with the map open. (but I believe the editor auto-compile should take care of it)
You do need to save actor A, which isn’t automatically marked as dirty even though the delegate list has been modified. We haven’t implemented that dirtying yet because it’s non-trivial to detect whether the delegate list has changed (cleanup and re-inserting happen at different stages during compilation). You can also recreate the problem again via your repro steps, by saving (A) and not (B). The problem is hard to avoid unless we enforce that two assets must be saved together, or rework how bindings are saved, but that’s not currently an engine feature.
Try out the fix for UE-273286, and please let me know how it goes. Also, let us know if the remaining problems are acceptable, i.e. you can work around them with validation. If not, I’ll bring it up for discussion with the rest of the team.
Thinking about this more, the cleanup of actor B from A’s multicast delegate only happens so long as B has that reference to A, and an event node for A’s event.
There are many ways to end up with a stale reference from A to B:
Saving instance A but not B, when B binds to A. Never changing B to bind to A again.
Hello again! I logged UE-366632 which should become publicly visible later this week. I logged all the details to repro this, when using the Events section of the details panel. The issue calls out both types of repro that we found:
On OFPA maps, not resaving the referenced actor (A) after clearing the reference (B->A).
Or deleting B’s event node that caused the binding.
I’m not sure yet what the longterm fix for the problem will be, but I imagine that validating the referenced actor’s Event Dispatcher state when it gets loaded is the only way to capture all cases. However, the challenge will be finding out which entries have a good reason for being in there.
Regarding a workaround to clean up dangling references on a case to case basis. Here is a snippet with an example of how to do that. You can run this snippet on any referenced actor instance, in an editor-time called function like PostLoad or OnConstruction to wipe the callback list. Then after doing that once, remove that code. Recompiling or nudging any referencing actors should place their valid entries back in the list again.
You may want to consider mandating the team to not use the Events section of the details panel, if you want to avoid the problem entirely, until UE-366632 is fixed.