Is it threadsafe to do an downcast actor to in a non game thread?

I downcast my actor (locked as AddToRoot using the guard from the example) from non IsInGameThread, and it seems to work, but sometimes crashes on seg fault inside Cast “guts”, although the actor may have been deleted due to a bug in my code (but it seems like it shouldn’t), I want to make sure that casts from non-game threads are a valid thing if the actor is alive and not subject to parallel access from other threads? (or maybe i have stupid bug in by guard?)

auto baseTile = Cast<ADTMBaseTileActor>(tile.Get()); // tile is a TGarbageCollectionShield

struct SharedGuard : public TSharedFromThis<SharedGuard>
{
    TSharedPtr<SharedGuard> GetSharedPtr()
    {
        return SharedThis(this);
    }
};

template<typename T>
class TGarbageCollectionShield
{
public:
    explicit TGarbageCollectionShield(T* aInObject = nullptr, bool aLockOnConstruct = true, bool aDestroyOnUnlock = false)
        : Object(aInObject)
        , SharedLocker(MakeShared<SharedGuard>())
        , ShouldDestroyOnUnlock(aDestroyOnUnlock)
    {
        if (Object && aLockOnConstruct)
        {
            //          UE_LOG(LogTemp, Display, TEXT("%s Guarded!"), *Object->GetName());

            Object->AddToRoot();
        }
    }

    [[maybe_unused]] bool Unlock()
    {
        bool wasUnlocked = false;

        if (Object && 1 == SharedLocker.GetSharedReferenceCount())
        {
            //            UE_LOG(LogTemp, Display, TEXT("%s Released!"), *Object->GetName());
            if (Object != nullptr)
            {
                if (Object->IsRooted())
                {
                    Object->RemoveFromRoot();
                }
            }
            else
            {
                return true;
            }

            if (ShouldDestroyOnUnlock)
            {
                //                UE_LOG(LogTemp, Display, TEXT("%s Destroyed!"), *Object->GetName());
                Object->Destroy();
            }
            wasUnlocked = true;
        }

        Object = nullptr;
        SharedLocker.Reset();

        return wasUnlocked;
    }

    ~TGarbageCollectionShield()
    {
        Unlock();
    }

    TGarbageCollectionShield(const TGarbageCollectionShield& aOther)
        : Object(aOther.Object), SharedLocker(aOther.SharedLocker), ShouldDestroyOnUnlock(aOther.ShouldDestroyOnUnlock)
    {
    }

    TGarbageCollectionShield& operator=(const TGarbageCollectionShield& aOther)
    {
        if (this != &aOther)
        {
            Object = aOther.Object;
            SharedLocker = aOther.SharedLocker;
            ShouldDestroyOnUnlock = aOther.ShouldDestroyOnUnlock;
        }
        return *this;
    }

    TGarbageCollectionShield(TGarbageCollectionShield&& aOther) noexcept
        : Object(MoveTemp(aOther.Object)), SharedLocker(MoveTemp(aOther.SharedLocker)), ShouldDestroyOnUnlock(MoveTemp(aOther.ShouldDestroyOnUnlock))
    {
        aOther.Object = nullptr;
    }

    TGarbageCollectionShield& operator=(TGarbageCollectionShield&& aOther) noexcept
    {
        if (this != &aOther)
        {
            Object = MoveTemp(aOther.Object);
            SharedLocker = MoveTemp(aOther.SharedLocker);
            ShouldDestroyOnUnlock = aOther.ShouldDestroyOnUnlock;

            aOther.Object = nullptr;
        }
        return *this;
    }

    bool operator==(const TGarbageCollectionShield& Other) const
    {
        return Object == Other.Object;
    }

    bool operator!=(const TGarbageCollectionShield& Other) const
    {
        return !(*this == Other);
    }

    T* Get() const { return Object; }

    template<typename U>
    friend class TGarbageCollectionShield;

    template<typename U>
    operator TGarbageCollectionShield<U>() const
    {
        U* castedObject = Cast<U>(Object);  // Assumes a Cast<U>(T*) function exists that is safe to use
        if (castedObject == nullptr)
        {
            return TGarbageCollectionShield<U>{nullptr, false};
        }

        auto shield = TGarbageCollectionShield<U>(castedObject, false); // Assume the object is already rooted
        shield.SharedLocker = SharedLocker;
        shield.ShouldDestroyOnUnlock = ShouldDestroyOnUnlock;
        return MoveTemp(shield);
    }


private:

    T* Object;
    TSharedPtr<SharedGuard> SharedLocker;

    bool ShouldDestroyOnUnlock { false };
};

No, there should be no problem casting objects up or down on a non-game thread. Problems with the objects are likely to be related to more conventional threading memory access issues.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.