Skip to content

Bug: 蓝图重编译后刷新缓存的废弃 UFunction - #2358

Open
Monocluar wants to merge 2 commits into
Tencent:masterfrom
Monocluar:fix/unreal-stale-function-after-recompile
Open

Bug: 蓝图重编译后刷新缓存的废弃 UFunction#2358
Monocluar wants to merge 2 commits into
Tencent:masterfrom
Monocluar:fix/unreal-stale-function-after-recompile

Conversation

@Monocluar

Copy link
Copy Markdown

问题说明

Blueprint 重编译时,Unreal Engine 会生成新的 Class,并将旧 Class 标记为:

CLASS_NewerVersionExists

旧 Class 中的 UFunction 会被迁移到临时类下,并等待后续 GC 回收。

在 GC 执行前,旧函数仍然满足:

Function.IsValid() == true

FFunctionTranslator::Call() 当前仅在函数弱指针失效时重新查找函数:

#if WITH_EDITOR
if (!CallFunction.IsValid())
{
    CallFunction = CallObject->GetClass()->FindFunctionByName(FunctionName);
    Init(CallFunction.Get(), false);
}
#endif

因此,旧函数被迁移到临时类后,如果尚未被 GC,FFunctionTranslator 仍会继续调用该函数。

对于被 Puerts 重定向的函数,旧函数可能进一步进入:

UTypeScriptGeneratedClass::execLazyLoadCallJS

此时函数所属 Class 已经不是原来的 UTypeScriptGeneratedClass,可能导致无效类型转换或空指针访问。

此外,原有函数刷新逻辑位于参数缓冲区分配之后。如果重编译修改了函数参数或返回值,刷新后的 ParamsBufferSize 与已经分配的参数缓冲区可能不一致。

修改内容

识别重编译产生的旧函数

在 Editor 环境中,调用缓存函数前检查其 Owner Class 是否带有:

CLASS_NewerVersionExists

如果函数所属 Class 已经被新版本替代,则使用初始化时保存的 FunctionName,从当前调用对象的 Class 中重新查找对应函数。

这里使用 ClassFlags 判断,不进行 TRASHCLASS_、REINST_ 等名称字符串检查,降低正常调用路径的额外开销。

调整函数刷新顺序

函数调用顺序调整为:

获取缓存函数
→ 检查函数所属 Class 是否已被替换
→ 从当前对象 Class 中刷新函数
→ 重新初始化 FFunctionTranslator
→ 分配参数缓冲区
→ 执行 FastCall 或 SlowCall

参数缓冲区在函数刷新之后分配,确保使用新函数签名对应的 ParamsBufferSize

使用刷新后的函数执行调用

FastCall 和 SlowCall 的选择及实际调用统一使用刷新后的 CallFunctionPtr,不再读取可能已经属于旧 Class 的缓存函数。

清理旧函数签名状态

FFunctionTranslator::Init() 可能在蓝图重编译后被再次调用,因此重新初始化前清理旧函数对应的:

  • 默认参数缓存;
  • 参数属性转换器;
  • 返回值属性转换器。

同时在构造函数中将默认参数缓存指针初始化为 nullptr,保证首次初始化和后续刷新路径的内存状态一致。

修改范围

本次修改仅涉及:

unreal/Puerts/Source/JsEnv/Private/FunctionTranslator.cpp

未修改公开 API,也不依赖具体项目类型、资产或业务逻辑。

性能影响

正常 Editor 调用路径只增加:

  • 一次函数 Owner Class 获取;
  • 一次 CLASS_NewerVersionExists 位标记判断。

只有检测到 Class 已被重编译替换时,才会执行 FindFunctionByName() 和 Init()。

非 Editor 构建不会执行旧 Class 检查。

在 Unreal Editor 中重新编译 Blueprint 或 `UTypeScriptGeneratedClass` 后,从 JavaScript 再次调用重编译前已经访问过的函数,可能发生崩溃。

崩溃时,Puerts 调用的 `UFunction` 已经被 Unreal Engine 蓝图编译器迁移到 `TRASHCLASS_` 临时类中,但该函数尚未被 GC,因此对应的 `TWeakObjectPtr<UFunction>` 仍然有效。
@Monocluar Monocluar closed this Aug 13, 2026
@Monocluar Monocluar reopened this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant