Hello! This is a crash we ran into! The following is our analysis of the problem and a code snippet to fix the issue:
Cause:
Even though MyWidget has a reference to MyBaseClass.MyBoolField through the MyDerivedClass viewmodel, MyWidget is not recompiled when MyBaseClass is recompiled, causing it to leave in stale field references in the blueprint code for MyWidget.
Recompiling MyBaseClass does cause MyDerivedClass to get “reinstanced” during which it goes through the functions of all it’s dependants (MyWidget) to attempt to update any changed variable references.
It is during this field replacement process the invalid field memory is accessed in the blueprint of MyWidget, because MyWidget was not compiled along with MyBaseClass and MyDerivedClass before “reinstancing”.
MyBaseClass does not show in the CachedDependencies list of MyWidget, which is a requirement for MyWidget to be recompiled when MyBaseClass is compiled. It isn’t there because the generation of the binding functions for MVVM happens at the very end of the blueprint compilation step, so whatever step that picks up blueprint references and puts it into CacheDependencies is not run after the generated blueprint functions are made. The MVVM module gets around this in UMVVMWidgetBlueprintExtension_View::HandleBeginCompilation by explicitly adding the blueprints of ViewModels to the CachedDependencies. It doesn’t add the blueprints of super classes though.
Workaround:
The workaround for this is to ensure the dependency to the other blueprints exist. This can be accomplished as simple as adding an accessor to the MyDerivedClassVM viewmodel property in the blueprint graph of MyWidget. This will cause the normal blueprint compiler process to pickup the dependency and add the classes to CachedDependencies.
Fix:
Expand UMVVMWidgetBlueprintExtension_View::HandleBeginCompilation by having it add blueprints of super classes of ViewModels to the CachedDependencies of the blueprint of MyWidget.
Real fix:
Ensure whatever process that normally picks up CachedDependencies from a blueprint can be run on the generated binding functions instead of explicitly adding the cached dependencies in HandleBeginCompilation.
This turns out to be caused by the fact the functions are generated, they never stay as “nodes” in the blueprint graphs, instead they’re directly converted into functions on the generated class.
By the time we’re generating the dependency graph between blueprints, the generated graphs are either not created yet or not available anymore.
Code:
void UMVVMWidgetBlueprintExtension_View::HandleBeginCompilation(FWidgetBlueprintCompilerContext& InCreationContext)
{
VerifyWidgetExtensions();
for (const FMVVMBlueprintViewModelContext& AvailableViewModel : BlueprintView->GetViewModels())
{
#if WITH_EDITORONLY_DATA
UWidgetBlueprint* WidgetBlueprint = GetWidgetBlueprint();
UClass* ViewModelClass = AvailableViewModel.GetViewModelClass();
if (WidgetBlueprint && ViewModelClass)
{
// BEGIN FIX:
UClass* TargetClass = ViewModelClass;
while (UBlueprint* ViewModelBP = Cast<UBlueprint>(TargetClass ? TargetClass->ClassGeneratedBy : nullptr))
{
ViewModelBP->CachedDependents.Add(WidgetBlueprint);
WidgetBlueprint->CachedDependencies.Add(ViewModelBP);
TargetClass = TargetClass->GetSuperClass();
}
// END FIX
} ....
[Attachment Removed]