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 };
};