UE4 TWeakObjectPtr 相关杂谈

    xiaoxiao2025-01-25  53

    UE4 TWeakObjectPtr<>相关杂谈


    前言

    因为某端游毒奶粉 又出新的角色了, 要肝 最近有点杂事, 所以很多计划都鸽子了, 计划就是用来鸽子的, 笑.所以有了这篇水字数的 文章, 当作这段时间鸽子的交代吧因为GC相关很复杂, 也没有能力深入研究, 所以, 也就这个样子了.文章吐槽大于其他, 不过这个bug, 无奈的笑.

    核心

    FReferenceCollector

    Helper class used by the garbage collector to collect object references. Object的垃圾回收帮助类, 可以参考这个理解, 但没有写任何相关的

    TWeakObjectPtr<>UPROPERTY()

    正文

    测试代码如下

    // .h virtual void BeginPlay() override; UPROPERTY(BlueprintReadOnly) class AActor* Test; TWeakObjectPtr<AActor> Test2; UPROPERTY(BlueprintReadOnly) class AActor* Test3; // .cpp void AGUAO_CPlusPlusCodeGameModeBase::BeginPlay() { Super::BeginPlay(); AActor* TempActor = GetWorld()->SpawnActor<AMyActor>(); Test = TempActor; AActor* TempActor2 = GetWorld()->SpawnActor<AMyActor>(); Test2 = TempActor2; AActor* TempActor3 = GetWorld()->SpawnActor<AMyActor>(); Test3 = TempActor3; FTimerHandle TimerHandle; GetWorld()->GetTimerManager().SetTimer(TimerHandle, [this]() { Test->Destroy(); Test2->Destroy(); Test3->Destroy(); Test3 = nullptr; }, 5.f, false); FTimerHandle TimerHandle2; GetWorld()->GetTimerManager().SetTimer(TimerHandle2, [this]() { if (Test) { UE_LOG(LogTemp, Log, TEXT("Test Actor 指针存在")); if (Test->IsPendingKillPending() || Test->IsUnreachable()) { UE_LOG(LogTemp, Log, TEXT("Test Actor 指针存在但已被销毁或无法访问")); } } else { UE_LOG(LogTemp, Log, TEXT("Test Actor 指针不存在")); } if (Test2.IsValid()) { UE_LOG(LogTemp, Log, TEXT("Test2 Actor 指针存在")); if (Test2->IsPendingKillPending() || Test2->IsUnreachable()) { UE_LOG(LogTemp, Log, TEXT("Test2 Actor 指针存在但已被销毁或无法访问")); } } else { UE_LOG(LogTemp, Log, TEXT("Test2 Actor 指针不存在")); } if (Test3) { UE_LOG(LogTemp, Log, TEXT("Test3 Actor 指针存在")); if (Test3->IsPendingKillPending() || Test3->IsUnreachable()) { UE_LOG(LogTemp, Log, TEXT("Test3 Actor 指针存在但已被销毁或无法访问")); } } else { UE_LOG(LogTemp, Log, TEXT("Test3 Actor 指针不存在")); } }, 2.f, true, 10.f); }

    创建三个Actor, 分别用UPROPERTY()和TWeakObjectPtr<>保存, 然后销毁Actor, 之后打印Actor指针对应的信息

    排除直接定义一个AActor* Test4形式, 因为会野指针崩溃.实际情况中会复杂很多, 创建销毁会在不同类里面, 甚至不同端(网络同步), 从而导致各种问题

    输出日志结果如下, 重点是12对比和13区别

    LogTemp: Test Actor 指针存在 LogTemp: Test Actor 指针存在但已被销毁或无法访问 LogTemp: Test2 Actor 指针不存在 LogTemp: Test3 Actor 指针不存在

    分析

    UE4 GC会有引用记数功能, 并为了避免循环引用, 有强弱引用之分. (详情见大象无形或者网络上其他分析, 细节懒得讲也讲不清), 例如 :

    A需要B, A被需要, 所以B不会被GCA只需要B, B只需要A, 但这个时候AB都不被需要, 这个时候AB应该被一起GC

    结论

    UPROPERTY是强引用, 会阻止GC

    13对比, 3指针置空后, 相当于释放引用, 引用数归0, 正常GC, 而1此时引用数为1, 没有被GC

    TWeakObjectPtr是弱引用, 不会阻止GC, 并会自动将GC后的指针至nullptr

    12对比, 都是直接销毁, 不做其他处理, 但2被GC并指针置空

    AActor::Destory() 会销毁Actor, 但不一定会被GC

    1就是例子


    然后这些结论会有什么用呢? 举一个可能会复杂点的例子

    UPROPERTY() class AHandTool* HandTool; // 手拾取工具, 如果工具存在, 调用工具的使用方法, 如果不存在, 调用拾取方法 if (HandTool) { HandTool->Use(); } else { TryPickUpHandTool(); }

    一切正常, 但突然出现特殊情况, HandTool在其他地方被直接销毁了

    会导致视图中工具不见了, 但这个时候指针还是存在的, 存在的, 存在的

    再次执行会继续调用HandTool的Use方法, 并且不能捡起其他的HandTool

    逻辑混乱了. bug了

    然后查, 这里检测了他是否存在了, 看代码的时候多少会直接忽略

    查不到啊? 小白无能为力了, 找老手去了

    老手会试着打断点, 会发现这个时候指针还存在, 问题定位到, 解决方案呢?

    一头懵, 为啥指针还存在, 不被置空呢, 谷歌各种找解决方法了

    杂 : 单机的时候是很好查的, 很容易直接定位到 但如果是联网呢, 你能直接确定是指针没有被置空, 而不是没有被销毁, 不是其他逻辑出了问题, 不是网络同步的先后顺序等等 查来查去什么时候才会定位到这里这个问题吗? 联网的断点也不好打的, 打日志也不好查的. 到最后不是试着避免绕过去或者就放着不管了

    别问问什么吐槽, 碰到这个问题, 挂了半年, 最后查的时候随手指针检测的时候加写了一个IsPendingKillPending()函数,引擎某些地方看到过, 然后正常了. 然后细致的想了想, 嗯, 想通了, 然而并没有什么卵用

    嗯, 大概就是这样了

    同时, 这个问题知道了会怎样呢, 将一些UPROPERTY()代替为TWeakObjectPtr<>吗, 很多地方不现实的 例如, 这个指针要和蓝图打交道. 例如, 这就是个蓝图变量(手动斜眼, 蓝图存在与否不知道, 但如果存在, 感觉会很糟糕, 笑)

    有没有好的解决办法呢, 没有, 笑.


    结语


    水完字数, 肝游戏忙去了最近可能会放很久鸽子, 因为肝游戏和准备下次10月份考试忙, 就看心情更新了.
    最新回复(0)