The InstancedStruct data in the blueprint cannot be automatically synchronized to the Actor of the level after the type is modified

The InstancedStruct variable was added to the blueprint and the type of its instance was set. Then, the blueprint was dragged to the level to form an Actor. At this point, modifying the instance type of this variable on the blueprint will not be automatically synchronized to the Actor of the level. It can only be manually synchronized by resetting this variable. ​

重现步骤
I have created a blueprint class named TestInstancedStruct that inherits from Actor and placed it in the Content directory. Meanwhile, in the level Lvl_ThirdPerson, there is also an Actor with the same name generated from this blueprint.

In the blueprint class TestInstancedStruct, there is a variable InstancedStruct, whose type is FInstancedStruct.

1. In the blueprint editing interface of TestInstancedStruct, modify the structure type of InstancedStruct. For example, if the default is Rotator, modify it to Vector;

2. Observe the Actor TestInstancedStruct in Lvl_ThirdPerson and find that its type has not changed, but it can be manually synchronized through Reset.

Hello [mention removed]​

I was able to reproduce the issue on my end consistently, so it seems like an engine bug.

I’ve found out that simply reloading the Level (after saving the parent BP) will reliably update all instanced actors with the new InstancedStruct variable type and value.

Before this issue gets addressed by Epic, in your BP’s parent native class, you could utilize delegates in the engine that should allow you to automatically update instances after the construction is completed, like FCoreUObjectDelegates::OnObjectsReinstanced or UEditorEngine::OnBlueprintReinstanced.

Using these delegates should be the quickest and most optimal solution for now.

Alternatively, I could investigate further to offer guidance on setting up a manual Editor Utility Widget solution, an EditorModule or a UE5.6 engine modification to handle InstancedStruct updates on all actor instances in the level for you.

Please let us know which approach would work best for you and if you need any further assistance!

Best regards,

Tadas

Hello, Tadas

Our expectation is to resolve this as soon as possible, so using Delegates is the preferred approach. However, we don’t fully understand the principles and usage methods of the two Delegates you mentioned.

Could you please use the reproduction project I submitted, or any other method that’s as intuitive as possible, to show us how to resolve this? Reference code would be even better.

Additionally, if this is confirmed to be an engine bug, when can we expect an official fix?

Best regards

Liuyang Chen

Hi [mention removed]​,

As Tadas said, it looks like the engine is not refreshing it correctly. The FCoreUObjectDelegates::OnObjectsReplaced that Tadas mentioned is triggered by the engine when a Blueprint is compiled, and all the old instances are replaced by the new ones. Looks like there is an issue with this process and it is not copying the values correctly. Luckly we can do a small work around to fix this issue:

First of all we would need to Add our function to the delegate. In my case I did in in the StartupModule of an Editor module. I set the LoadingPhase of the Editor module to “PostEngineInit”. I’m not sure if it is needed to register the delegate but I did it just in case:

{
			"Name": "EM_SC_56",
			"Type": "Editor",
			"LoadingPhase": "PostEngineInit"
}
void FEM_SC_56Module::StartupModule()
{
	FCoreUObjectDelegates::OnObjectsReplaced.AddRaw(this, &FEM_SC_56Module::OnInstancesReplaced);
}

And then also the implementation of our function. What the function does is that it travels all the instances of the compiled blueprint and check for InstancedStruct properties and if they are found, it will copy the values from the Blueprint to the instances. This should work for all of your blueprints:

void FEM_SC_56Module::OnInstancesReplaced(const FCoreUObjectDelegates::FReplacementObjectMap& ReplacementMap)
{
	for (const TPair<UObject*, UObject*>& Pair : ReplacementMap)
	{
		UObject* NewObj = Pair.Value;
		if (!NewObj)
		{
			continue;
		}
 
		if (NewObj->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))
		{
			continue;
		}
 
		UClass* Cls = NewObj->GetClass();
		if (!Cls)
		{
			continue;
		}
 
		UObject* CDO = Cls->GetDefaultObject(false);
		if (!CDO)
		{
			continue;
		}
 
		for (TFieldIterator<FProperty> It(Cls); It; ++It)
		{
			FProperty* Prop = *It;
 
			FStructProperty* StructProp = CastField<FStructProperty>(Prop);
			if (!StructProp || StructProp->Struct != FInstancedStruct::StaticStruct())
			{
				continue;
			}
 
			void* InstData  = StructProp->ContainerPtrToValuePtr<void>(NewObj);
			void* CDOData   = StructProp->ContainerPtrToValuePtr<void>(CDO);
 
			FInstancedStruct* InstValue = reinterpret_cast<FInstancedStruct*>(InstData);
			FInstancedStruct* CDOValue  = reinterpret_cast<FInstancedStruct*>(CDOData);
 
			if (!InstValue || !CDOValue)
			{
				continue;
			}
 
			const UScriptStruct* InstStructType = InstValue->GetScriptStruct();
			const UScriptStruct* CDOStructType  = CDOValue->GetScriptStruct();
 
			if (InstStructType != CDOStructType)
			{
				NewObj->Modify(); 
 
				StructProp->CopyCompleteValue(InstData, CDOData);
			}
		}
	}
}

With this function you should have your issue fixed. We can report this bug to Epic but we can’t give you a date for when is Epic going to work on it as that is managed by Epic developers and sadly we don’t have information about that. Let us know if it solved your issue :wink:

Best,

Joan