[언리얼엔진] TSoftObjectPtr::IsValid

    시작하며

    에디터 모드(PIE)에서는 정상적으로 동작되었지만 릴리즈 모드 에서는 아이콘이 적용되지 않는 이슈가 발생하였다. 따라서 해당 이슈 분석 결과를 기록하고자 한다.

    에디터모드 vs 릴리즈모드에 따른 SoftObjectPtr의 IsValid 함수 사용 이슈

    • TSoftObjectPtr를 이용하여 애셋을 참조하는 경우 다음과 같이 조건 처리하였다.
        TSoftObjectPtr<UObject> WeaponIcon = GetWeaponIcon(WeaponType);
        if( WeaponIcon.IsValid())
        {
            FString IconPath = WeaponIcon.ToString();
    
            //IconPath로 아이콘 리소스 교체 중략
        }
    • 위 코드 결과 TSoftObjectPtr의 IsValid 함수 사용 시 모드에 따라 결과값이 달라 이슈가 발생하였다.
      • 에디터 모드(PIE)는 true / 릴리즈 모드에서는 false가 반환

    TSoftObjectPtr::IsValid()

    • IsValid함수 구현부는 다음과 같다
    • Get()함수를 호출하여 null 체크를 진행한다.
    template<class TClass=UObject>
    class TSoftClassPtr
    {
        ...
    
        /**  
            * Test if this points to a live UObject
            *
            * @return true if Get() would return a valid non-null pointer
            */
        FORCEINLINE bool IsValid() const
        {
            // This does the runtime type check
            return Get() != nullptr;
        }
    
        ...
    }
    • Get함수를 들어가보자.
    • TSoftObjectPtr의 FSoftObjectPtr 타입 멤버 변수 SoftObjectPtr의 Get함수를 반환하고 있다.
    template<class TClass=UObject>
    class TSoftClassPtr
    {
        ...
    
        /**
            * Dereference the soft pointer.
            *
            * @return nullptr if this object is gone or the lazy pointer was null, otherwise a valid UObject pointer
            */
        FORCEINLINE T* Get() const
        {
            return dynamic_cast<T*>(SoftObjectPtr.Get());
        }
    
        ...
    }

    에디터 환경에서 FSoftObjectPtr의 Get

    • FSoftObjectPtr의 Get함수가 WITH_EDITOR 로 감싸져 있다.
    • 에디터 환경에서는 (아마도?) 내부에서 애셋을 로드해주고 유효한 포인터를 반환하는 것으로 추정된다.
    struct FSoftObjectPtr : public TPersistentObjectPtr<FSoftObjectPath>
    {
        ...
    
    #if WITH_EDITOR
        /** Overridden to deal with PIE lookups */
        FORCEINLINE UObject* Get() const
        {
            if (GPlayInEditorID != INDEX_NONE)
            {
                // Cannot use or set the cached value in PIE as it may affect other PIE instances or the editor
                TWeakObjectPtr<UObject> Result = GetUniqueID().ResolveObject();
                // If this object is pending kill or otherwise invalid, this will return nullptr just like TPersistentObjectPtr<FSoftObjectPath>::Get()
                return Result.Get();
            }
            return TPersistentObjectPtr<FSoftObjectPath>::Get();
        }
    #endif
        ...
    }

    릴리즈 환경에서 FSoftObjectPtr의 Get

    • 릴리즈모드에서는 FSoftObjectPtr의 부모클래스인 TPersistentObjectPtr의 Get함수가 호출한다
    • 저 검사를 하고 Path를 받아 로드를 하는데 당연히 아직 로드되기 전이기 때문에 nullptr이 반환된다.
    • 언리얼 문서의 애셋 참조 페이지를 보면 TSoftObjectPtr 을 사용하려면 애셋을 수동으로 로드해야 합니다. 라고 적혀있다.
    template<class TObjectID>
    struct TPersistentObjectPtr
    {
        ...
    
        /**
            * Dereference the pointer, which may cause it to become valid again. Will not try to load pending outside of game thread
            *
            * @return nullptr if this object is gone or the pointer was null, otherwise a valid UObject pointer
            */
        FORCEINLINE UObject* Get() const
        {
            UObject* Object = WeakPtr.Get();
    
            // Do a full resolve if the returned object is null and either we think we've loaded new objects, or the weak ptr may be stale
            if (!Object && ObjectID.IsValid() && (TObjectID::GetCurrentTag() != TagAtLastTest || !WeakPtr.IsExplicitlyNull()))
            {
                Object = ObjectID.ResolveObject();
                WeakPtr = Object;
    
                // Not safe to update tag during save as ResolveObject may have failed accidentally
                if (Object || !GIsSavingPackage)
                {
                    TagAtLastTest = TObjectID::GetCurrentTag();
                }
    
                // If this object is pending kill or otherwise invalid, this will return nullptr as expected
                Object = WeakPtr.Get();
            }
            return Object;
        }
    
        ...
    }

    이슈 해결

    • 에디터 모드와 릴리즈 모드 양쪽에서 애셋 참조 유효성을 체크하려면  IsValid 가 아니라 IsNull 로 체크해야 한다.
        TSoftObjectPtr<UObject> WeaponIcon = GetWeaponIcon(WeaponType);
        if( WeaponIcon.IsNull())
        {
            FString IconPath = WeaponIcon.ToString();
    
            //IconPath로 아이콘 리소스 교체 중략
        }

    요약

    TSoftObjectPtr에 데이터가 들어있는지 확인하려면 IsValid가 아닌 IsNull 함수로 체크하자.

    반응형

    댓글

    Designed by JB FACTORY