#Instanced structs in DevSettings

1 messages · Page 1 of 1 (latest)

dense steppe
#

I spent last night debugging. It is indeed a bug in engine, at least in 5.5.

#

diving in deeper from saving devsettings to ini in reverse order:

  • SSettingsEditor::NotifyPostChange is what triggers saving, but GetNumObjectsBeingEdited returns 0 for FInstancedStruct and nothing happens. Editor expects it to be UDevSettings but TopLevelObjects is empty
  • SSettingsEditor::NotifyPostChange invoked from FPropertyNode::NotifyPostChange
  • FPropertyNode::NotifyPostChange is invoked from FPropertyValueImpl::ImportText where FPropertyValueImpl::GetObjectsToModify collects list of modified objects
  • FPropertyValueImpl::GetObjectsToModify calls FPropertyValueImpl::EnumerateObjectsToModify
  • FPropertyValueImpl::EnumerateObjectsToModify has a weird logic - instanced struct is being treated like a standalone struct and not member of uobject
#

In this case and FObjectBaseAddress.Object not being populated (has to be UDevSettings instance)

            const bool bIsStruct = FComplexPropertyNode::EPT_StandaloneStructure == ComplexNode->GetPropertyType();
            UObject* Object = nullptr;
            if (bIsStruct) 
            { // this part is used for FInstancedStruct, ComplexNode=FStructurePropertyNode with FInstancedStructProvider
              // it's ParentNode FObjectPropertyNode containing UDevSettings (being accessed by FInstancedStructProvider)
                StructAddress = ComplexNode->GetMemoryOfInstance(Index);
            }
            else
            { // for regular struct member ComplexNode=FObjectPropertyNode, Object is UDevSettings
                Object = ComplexNode->GetInstanceAsUObject(Index).Get();
                StructAddress = InPropertyNode->GetStartAddressFromObject(Object);
            }
#

and then FPropertyValueImpl::ImportText uses that data to determine modified object

            UObject* CurObject = Cur.Object; // object being modified (DevSettings)
            const bool bIsStruct = !Cur.Object; // why not check for EPT_StandaloneStructure ???????

            TopLevelObjects.Add(CurObject); // here adding top level object that is checked in SSettingsEditor later
            
            
            // later event constructed and populated without any objects 
            FPropertyChangedEvent ChangeEvent(..., MakeArrayView(TopLevelObjects));
    
    // there is no point checking FPropertyNode::NotifyPostChange further as ChangeEvent does not change and lacking data about base object 
#

fixing manually FObjectBaseAddress.Object and how bIsStruct determined later seems to fix resulting the value in TopLevelObjects for both regular and container use
i'd say a new virtual has to be added to ComplexNode and forwarded to StructProvider to test if it is a standalone or a indirect member

#

workaround - just save it manually with this universal fixer:

#if WITH_EDITOR
void USampleDeveloperSettings::PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent)
{
    Super::PostEditChangeChainProperty(PropertyChangedEvent);
    // perform forced save only on FInstancedStruct property change
    // instead of name can check for being instanced struct type
    if (PropertyChangedEvent.PropertyChain.GetHead()->GetValue()->GetName() == GET_MEMBER_NAME_CHECKED(ThisClass, Sample))
    { // it is safe to directly call Save as due to bug SSettingsEditor::NotifyPostChange wont work due to PropertyChangedEvent.TopLevelObject being empty
        auto& SettingsModule = FModuleManager::GetModuleChecked<ISettingsModule>("Settings");
        auto Section = SettingsModule.GetContainer(GetContainerName())->GetCategory(GetCategoryName())->GetSection(GetSectionName());
        Section->Save();
    }
}
#endif

PostEditChangeChainProperty is invoked on DevSettings instance prior to NotifyPostChange, but SSettingsEdior will never handle it because event lacks of object

I've tested workaround fix for regular and arrays - worked fine, well. it resaves entire section when IS changes