fix(unreal): TS 函数库静态方法生成为静态 UFunction,并修正函数库蓝图的 BlueprintType - #2354
Open
ZhiruiLi wants to merge 2 commits into
Open
fix(unreal): TS 函数库静态方法生成为静态 UFunction,并修正函数库蓝图的 BlueprintType#2354ZhiruiLi wants to merge 2 commits into
ZhiruiLi wants to merge 2 commits into
Conversation
…nt functions Static methods of TypeScript classes derived from BlueprintFunctionLibrary were generated as instance UFunctions. Blueprint call nodes then exposed a Target pin, and the runtime only wires JS dispatch for FUNC_Static functions, so the TS implementation was never invoked. Set FUNC_Static when the method declaration has the static modifier, and explicitly clear it otherwise so regeneration converges when the modifier is added or removed. This matches how the UE editor marks functions created in a blueprint function library (SBlueprintPalette adds FUNC_Static for BPTYPE_FunctionLibrary).
LoadOrCreate derives BPTYPE_FunctionLibrary from the parent class, but only on the create path, and UPEClassMetaData::SyncClassToBlueprint then resets BlueprintType using @Uclass flags alone, which only knows Const and Normal. Since the metadata sync runs right after LoadOrCreate inside LoadOrCreateWithMetaData, a function library blueprint is downgraded to BPTYPE_Normal within the same call and never recovers. With the wrong type, UEdGraphSchema_K2::IsStaticFunctionGraph falls back to probing FUNC_Static on the function entry node, so the implicit hidden __WorldContext parameter follows that flag instead of the blueprint type, and the function library fallbacks in FastGenerateSkeletonClass stay disabled. The serialized generated class and the skeleton class, rebuilt on every load, then disagree on the signature: callers fail to compile with "Could not find a pin for the parameter __WorldContext", and Refresh Nodes cannot fix it because pin creation and signature validation resolve different classes. Derive BlueprintType from the parent class on the load path as well, and stop the metadata sync from overwriting the function library case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题现象
TS 类继承
UE.BlueprintFunctionLibrary并用static方法定义函数库函数时:Target引脚,与 C++/蓝图定义的函数库静态函数"无需 Target、直接调用"不一致;即使连上 Target 强行调用,执行的也是空函数体,TS 实现永远不会被调用。Could not find a pin for the parameter __WorldContext of <FuncName>。对调用节点执行 Refresh Nodes 无效;手动编译一次函数库蓝图可临时恢复;删除生成蓝图后首次启动正常、再次启动必现。两者根因不同,但同源于生成端对"函数库"这一语义的处理缺失,因此一并修复。
根因分析
一、生成端从不产出
FUNC_Static蓝图节点是否需要
Target引脚取决于编译出的UFunction是否带FUNC_Static,而整条生成链路都没有它:PuertsEditor/CodeAnalyze.ts的onBlueprintTypeAddOrChange能识别方法的static修饰符(并强制函数库中的方法必须为 static),但计算函数 flags 时只收集//@flags:注解和@flags()/set_flags()/clear_flags()装饰器,从不把static映射为FUNC_Static;UEMeta.ts的@ufunction.ufunction(...)specifier 表中没有 Static(与 UE 一致:C++ 中 static 是语言关键字,不是 UFUNCTION specifier),元数据路径同样不会产出该标志;UPEBlueprintAsset::AddFunction只把传入 flags 写进函数入口节点的ExtraFlags,由 Kismet 编译器合并进UFunction::FunctionFlags——入口没有,产物自然没有。而运行时对静态函数的支持其实是完备的(
JsEnvImpl.cpp):模块注入时静态函数从 JS 类构造器上查找方法(TS 的 static 方法挂在构造器而非 prototype 上);TryBindJs对BlueprintFunctionLibrary派生类的 CDO 只为带FUNC_Static的函数挂接execLazyLoadCallJS;调用时以undefined作为 this 调用 JS 方法。即运行时早已就绪,仅因生成端缺少该 flag 而整条链路成为死代码。UE 编辑器自身为函数库新增函数时也会显式补
FUNC_Static(SBlueprintPalette.cpp:BPTYPE_FunctionLibrary时ExtraFunctionFlags |= FUNC_Static),本修复正是对齐这一行为。二、函数库蓝图的
BlueprintType一直是BPTYPE_Normal实测生成出的 TS 函数库蓝图,
ParentClass是BlueprintFunctionLibrary,但BlueprintType全部为BPTYPE_Normal。两处叠加造成:UPEClassMetaData::SyncClassToBlueprint按@uclassflags 重置类型:(InClass->ClassFlags & CLASS_Const) ? BPTYPE_Const : BPTYPE_Normal,没有函数库这一档。它在LoadOrCreateWithMetaData中于LoadOrCreate之后执行,因此创建分支按父类选出的BPTYPE_FunctionLibrary会在同一次调用内被改回BPTYPE_Normal;UPEBlueprintAsset::LoadOrCreate只在创建分支按父类推导BlueprintType,加载已有资产的分支只校正ParentClass,不校正类型,因此错位状态无法自愈。三、错位如何导致
__WorldContext编译错误隐藏的
__WorldContext引脚由K2Node_FunctionEntry::AllocateDefaultPins→FFunctionEntryHelper::RequireWorldContextParameter→UEdGraphSchema_K2::IsStaticFunctionGraph决定,后者有两条分支:函数库蓝图恒为真;否则退化为探测入口节点的FUNC_Static。BlueprintType错位为BPTYPE_Normal时只能走第二条分支,于是:FUNC_Static,而不是跟随蓝图类型;bIsStaticFunction = (BPTYPE_FunctionLibrary == BP->BlueprintType)为假,FastGenerateSkeletonClass中为函数库准备的兜底(硬编码补FUNC_Static、__WorldContext参数、MD_WorldContext元数据)全部失效,skeleton 的参数完全来自入口节点引脚。两个类的更新节奏并不相同:
SkeletonGeneratedClass每次加载都按当前入口节点引脚重建,GeneratedClass是上一次 full 编译时的签名并序列化到磁盘。而AddFunction先创建函数图与入口节点(此时FUNC_Static尚未写入 → 没有__WorldContext引脚),之后才写入 flags 并编译,因此:FUNC_Static新增该引脚 → 每次加载重建的 skeleton 都带上该参数,磁盘上的 generated 仍是旧签名 → 必现报错。编译调用方蓝图时,被调蓝图非 up-to-date 时签名校验会经
FBlueprintEditorUtils::GetMostUpToDateClass重定向到 skeleton(CallFunctionHandler.cpp),按其每个参数在节点引脚中查找,找不到__WorldContext即报此错。Refresh Nodes 无效是因为建引脚(UK2Node_CallFunction::AllocateDefaultPins)与签名校验解析到的类不同——注意引脚创建对每个参数是无条件的,GetHiddenPinsForFunction只负责置bHidden,所以节点缺该引脚只能是当时解析到的签名里确实没有。修复方案
1.
CodeAnalyze.ts:按static修饰符设置/清除FUNC_Static静态方法(仅允许出现在
BlueprintFunctionLibrary派生类中,上方已有过滤)置FUNC_Static;非静态方法显式清除该位——AddFunction合并 flags 时会保留入口节点上的旧ExtraFlags,显式清除保证删除static后重新生成能收敛。2.
PEBlueprintAsset.cpp:加载分支按父类校正BlueprintType与创建分支使用同一规则,不一致时置
NeedSave触发重新编译与保存,使已有资产能自愈。3.
PEBlueprintMetaData.cpp:元数据同步不再覆盖函数库类型职责划分:是否函数库由
LoadOrCreate依父类推导(唯一真源),@uclassflags 只决定 Const / Normal。仅改第 2 处无效——元数据同步会在同一次LoadOrCreateWithMetaData调用内把它重置回去。影响面
__WorldContext隐式参数由蓝图类型稳定决定,不再随 flag 漂移;clearFlags |= FUNC_Static为幂等无操作——此前没有任何路径会给这些函数写入该位,且生成的函数节点不允许用户在编辑器手动修改 static;do not support static function警告拦截,行为不变;BlueprintType的目标值仍是 Const / Normal,行为不变;FKismetCompilerContext::CreateVariable报VariableInFunctionLibrary),因此继承UE.BlueprintFunctionLibrary的 TS 类不应声明属性字段;NeedSave→ 重新编译并保存)。修复前放置的调用节点需要重建一次引脚(签名从"无__WorldContext"变为"有")——修复后两个类签名一致,Refresh Nodes 才真正有效,刷新后保存该蓝图即可。验证
在 UE5 工程中实测:
class TS_XxxFunctionLibrary extends UE.BlueprintFunctionLibrary,包含@ufunction.ufunction(ufunction.BlueprintCallable) static Foo(): void及带参重载;BlueprintType全为BPTYPE_Normal,调用节点带Target引脚,调用方蓝图编译报Could not find a pin for the parameter __WorldContext;BPTYPE_FunctionLibrary,无新增编译错误(无VariableInFunctionLibrary、无LogBlueprint错误);调用节点不再出现Target引脚,形态与 C++ 函数库静态函数一致;调用方蓝图编译通过,__WorldContext报错消失;FFunctionTranslator的SkipWorldContextInArg0已处理无声明参数时__WorldContext位于首参的情形,有声明参数时它位于参数列表末尾、由 JS 侧自然忽略)。涉及文件
unreal/Puerts/PuertsEditor/CodeAnalyze.tsstatic修饰符设置/清除FUNC_Staticunreal/Puerts/Source/PuertsEditor/Private/PEBlueprintAsset.cppLoadOrCreate加载分支按父类校正BlueprintTypeunreal/Puerts/Source/PuertsEditor/Private/PEBlueprintMetaData.cppSyncClassToBlueprint不再把函数库蓝图降级为BPTYPE_Normal