Description

There is currently no simple way to do a deprecated variable fixup on native variables that are set by a blueprint class. For a simple variable rename, CoreRedirects can be used and will work properly. However for a more complex fixup the general procedure is to set up a custom version (or a class-specific version which has the same problem) and add fixup logic to PostLoad. This will work properly for native classes that are placed or blueprint classes that are not placed or inherited, but it the deprecation behavior will be inconsistent for blueprint classes that are placed or inherited.

The primary reason this does not work consistently is that the timing of PostLoad depends on the order that assets are loaded. If the blueprint is loaded before the map as in step 13, the deprecation logic in PostLoad on the blueprint happens before the instance is loaded and it works properly. However if the map and blueprint are loaded at the same time like in step 11, the PostLoad on the blueprint will happen after the instance has already been partially loaded so it will load in with the incorrect value.

There are several work arounds for this problem but they do not cover all cases. One option is to move the deprecation fixup to Serialize instead of PostLoad, and this works for most cases but is not guaranteed to work for blueprint inheritance because blueprint CDOs do not call properly call Serialize for historical reasons. Another option is to call the fixup logic from PostCDOCompiled instead of PostLoad, but that is complex and requires checking the bIsRegeneratingOnLoad flag before running the fixup as the linker version will be incorrect for compiles without that flag.

Data refactoring would be easier if there was a simple and reliable method for upgrading deprecated blueprint-exposed variables.

Steps to Reproduce

This repro uses EngineTest but you can modify it for any game:

  1. Load EngineTest in the editor
  2. Create a new blueprint (TestPropertyBP) out of the class EngineTestPropertiesActor
  3. Modify the IntegerProperty on TestPropertyBP to be 1, and then save the blueprint
  4. Place an instance of that blueprint in the default CornellBox map. Save the map
  5. Open up EngineTestPropertiesActor.h and modify the header stating on line 60 to deprecate IntegerProperty, add a new property, and add a PostLoad and serialize function override before the Int64property
    UPROPERTY()
    int32 IntegerProperty_DEPRECATED;
    
    UPROPERTY(EditAnywhere, Interp, BlueprintReadWrite, Category = "General")
    int32 NewIntegerProperty;
    
    virtual PostLoad() override;
    virtual void Serialize(FArchive& Ar) override;
  6. Open up EngineTestPropertiesActor.cpp and go to the bottom. Add this code to declare a custom version, post load, and serialize functions to handle deprecation fixup:
    struct FTestVersion
    {
     enum Type
     {
     BeforeCustomVersionWasAdded = 0,
     NewIntegerProperty = 1,
     VersionPlusOne,
     LatestVersion = VersionPlusOne - 1
     };
     const static FGuid GUID;
    };
    const FGuid FTestVersion::GUID(0x11310AED, 0x2E554D61, 0xAF679AA3, 0xC5A1083C);
    FCustomVersionRegistration GRegisterReproCustomVersion(FTestVersion::GUID, FTestVersion::LatestVersion, TEXT("TestVersion"));
    void AEngineTestPropertiesActor::Serialize(FArchive& Ar)
    {
     Super::Serialize(Ar);
     Ar.UsingCustomVersion(FTestVersion::GUID);
    }
    void AEngineTestPropertiesActor::PostLoad()
    {
     Super::PostLoad();
     const int32 TestVersion = GetLinkerCustomVersion(FTestVersion::GUID);
     if (TestVersion < FTestVersion::NewIntegerProperty)
     {
     NewIntegerProperty = IntegerProperty_DEPRECATED;
     }
     if (TestVersion >= FTestVersion::NewIntegerProperty)
     {
     IntegerProperty_DEPRECATED = NewIntegerProperty;
     }
    }
  7. Compile and load EngineTest in the editor
  8. Select the TestPropertyBP instance you placed in CornellBox and verify that NewIntegerProperty is 1, which means the deprecation fixup worked
  9. Open TestProperty and save it, this upgrades the version on the blueprint
  10. Close the editor and reload it
  11. Select the TestPropertyBP instance you placed in CornellBox and look at the value of NewIntegerProperty. If this bug still exists, the value will be 0 because the deprecation fixup failed because the map and BP loaded at the same time
  12. Go to the File menu, click recent maps, and reload the CornellBox map
  13. Select TestPropertyBP instance you placed in CornellBox and look at the value of NewIntegerProperty, it will be 1 because the map was loaded after the blueprint

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-209248 in the post.

0
Login to Vote

Unresolved
ComponentUE - Gameplay - Blueprint
Affects Versions5.05.5
Target Fix5.5
CreatedMar 7, 2024
UpdatedMar 11, 2024