Tencent / puerts

PUER(普洱) Typescript. Let's write your game in UE or Unity with TypeScript.
Other
5.04k stars 702 forks source link

[UE] Bug: Puerts_UserObjectRetainer 持有EditorWorld引用导致Editor切地图时崩溃。 #1552

Closed BlurryLight closed 1 year ago

BlurryLight commented 1 year ago

前置阅读 | Pre-reading

Puer的版本 | Puer Version

1.0.5

UE的版本 | UE Version

All

发生在哪个平台 | Platform

Editor(win)

错误信息 | Error Message

image

问题重现 | Bug reproduce

    JsEnv = MakeShared<puerts::FJsEnv>();
    TArray<TPair<FString, UObject*>> Arguments;
    Arguments.Add(TPair<FString, UObject*>(TEXT("GWorld"), GWorld));
    JsEnv->Start("QuickTest", Arguments);

一个最简单可复现的例子。 JSEnv是外部保存的某个SharedPtr,把当前的EditorWorld传给ts时,会触发JsEnv::UserObjectRetainer::Retain,将这个UWorld添加一次引用。在这种情况下切地图,虚幻会crash,因为当前EditorWorld被JsEnv引用着。

项目里实际碰见的情况比这个复杂一点,但是原因是一样的: 某些函数调用可能返回了EditorWorld。

一个简单的修复是在FObjectRetainer::Retain的时候排除UWorld对象。

chexiongsheng commented 1 year ago

过滤UWorld比较丑而且也没解决问题:我记得持有场景的任意待销毁对象都会有同样的问题。 我觉得较合理的做法是:首先业务得保证自己切场景前在js不持有场景上的对象(你举的argv,puerts有问题,也要修改下,执行玩Start脚本后应该清理argv对象);然后执行下js虚拟机的gc。

BlurryLight commented 1 year ago

感觉只要在js里获得过EditorWorld 且 JsEnv不是随用随弃的情况下都会有问题。

比如这样的情况,切地图就会崩

// cpp
UWorld* UToolBPLibrary::GetEditorWorld()
{
    for (auto& It: GEngine->GetWorldContexts())
    {
        UWorld* World = It.World();
        if(!World) continue;
        if(World->WorldType == EWorldType::Editor)
        {
            return World;
        }
    }
    return GWorld;
}

// QuickStart.js
import * as UE from 'ue'
UE.ToolBPLibrary.GetEditorWorld()
chexiongsheng commented 1 year ago

切地图前你试试进行一次全量的js gc,JsEnv上的LowMemoryNotification(只是告知,v8的gc不一定会执行Full gc),RequestFullGarbageCollectionForTesting

BlurryLight commented 1 year ago

手动调用FullGC后可以解决问题,会通过FJsEnvImpl::UnBind释放引用。