ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PUERts(Unreal)缺陷修复剖析:BindInfoMap 指针失效崩溃与结构体字段缓存问题

PUERts(Unreal)缺陷修复剖析:BindInfoMap 指针失效崩溃与结构体字段缓存问题 PUERtsUnreal缺陷修复剖析BindInfoMap 指针失效崩溃与结构体字段缓存问题【免费下载链接】puertsPUER(普洱) Typescript. Lets write your game in UE or Unity with TypeScript.项目地址: https://gitcode.com/GitHub_Trending/pu/puerts本文基于 PUERts 仓库中 Unreal 端的官方缺陷记录 bugs.md逐条剖析 v1.0.0 / v1.0.1 版本中两个典型底层缺陷的成因与修复方式BindInfoPtr因 TMap rehash 指向无效数据导致的崩溃以及结构体字段访问缓存引发的内存泄漏和FBox2D首字段访问错位问题。读完后你将理解 PUERts 在 V8 与 UE 类型系统之间的绑定数据结构FBindInfo/BindInfoMap掌握“容器迭代器/指针失效”“缓存生命周期管理”这类 C 运行时工程中的通用陷阱并能在当前源码中定位到相应的修复点。缺陷清单概览doc/unreal/zhcn/bugs.md 按版本记录了 PUERtsUnreal 端发布后确认的缺陷当前收录如下版本缺陷类型v1.0.1BindInfoPtr可能指向无效数据进而导致崩溃内存安全 / 崩溃v1.0.0结构体类型的字段访问的内存泄漏内存泄漏v1.0.0静态绑定含结构体字段的结构体比如FBox2D的首字段的字段访问失败功能错误其中前两项都与结构体/类的“字段缓存”和“绑定信息表”这两块 PUERts 性能优化相关第三条则直接暴露了一个可复现的脚本行为错误。下面逐条展开并结合当前仓库源码印证修复后的实现。背景BindInfoMap 在绑定流程中的角色在深入缺陷之前先理解这两个缺陷涉及的共享基础设施。从源码结构看PUERts 的 V8 环境实现FJsEnvImpl中维护了一张 TypeScript 类到 V8 绑定信息的映射表// JsEnvImpl.h struct FBindInfo { FName Name; v8::UniquePersistentv8::Function Constructor; v8::UniquePersistentv8::Object Prototype; bool InjectNotFinished; }; TMapUTypeScriptGeneratedClass*, FBindInfo BindInfoMap;定义见 JsEnvImpl.h。FBindInfo保存了每个 TypeScript 生成类对应的 V8 构造函数Constructor与原型对象Prototype的持久句柄以及注入完成标志。每当 C 侧构造出一个属于 TypeScript 类的UObject时例如蓝图实例化了 TS 类环境会通过BindInfoMap.Find(Class)取得对应的绑定信息把新对象的 V8 包装设置到该类的 Prototype 上并调用构造函数——这正是“C 对象 ↔ JS 对象”双向桥接的关键路径调用点位于 JsEnvImpl.cpp。适用前提本文分析的绑定流程代码位于JsEnv模块UE V8 后端适用于使用 V8 后端的 PUERts Unreal 版本缺陷记录本身针对 v1.0.0 / v1.0.1 两个发布版本当前仓库源码已包含相应修复。v1.0.1BindInfoPtr 指向无效数据导致的崩溃问题描述官方记录指出std::map替换为TMap的优化没有考虑到FindOrAdd会引发BindInfoMap的 rehash进而导致BindInfoPtr失效。拆开来说这是一个典型的容器指针失效问题优化前绑定信息表使用的是std::map树结构向其中插入新元素不会使已有元素的迭代器或指针失效优化为 UE 原生的TMap哈希表后插入新键值对可能触发桶重分配rehash此时所有指向表内元素的指针、引用、迭代器全部作废而绑定流程中先通过BindInfoMap.Find(Class)拿到了一个指向FBindInfo的裸指针BindInfoPtr随后调用了可能向表中插入新条目的FindOrAdd为 C 对象创建 V8 包装时可能顺带触发新类的注入/建表插入导致 rehash 后继续解引用旧的BindInfoPtr读到的就是无效内存——轻则数据错乱重则直接崩溃。修复方式修复的核心思路是在每次可能修改BindInfoMap的调用之后重新 Find 刷新指针。当前源码中可以清晰看到这一修复的注释与实现// JsEnvImpl.cppC 对象构造 TS 包装的路径 auto PersistentValuePtr ObjectMap.Find(Object); if (!PersistentValuePtr) { JSObject FindOrAdd(Isolate, Context, Object-GetClass(), Object, true) -ToObject(Context).ToLocalChecked(); // FindOrAdd may change BindInfoMap, cause a rehash BindInfoPtr BindInfoMap.Find(Class); } else { JSObject PersistentValuePtr-Get(Isolate).Asv8::Object(); }见 JsEnvImpl.cpp。同样的“先 Find、再在注入完成后重新 Find”的防护模式也出现在类注入完成路径FinishInjection/MakeSureInject附近JsEnvImpl.cppauto BindInfoPtr BindInfoMap.Find(Class); if (!BindInfoPtr || BindInfoPtr-InjectNotFinished) { MakeSureInject(Class, true, false); if (!IsCDO) FinishInjection(Class); BindInfoPtr BindInfoMap.Find(Class); // 注入可能修改了表重新获取 }MakeSureInject内部会创建并写入FBindInfoJsEnvImpl.cpp这本身就是向BindInfoMap插入条目的操作因此其后必须重新Find才能保证指针有效。工程启示这一缺陷是 C 容器使用中的经典陷阱适用于所有类似场景TMap/std::unordered_map等哈希容器插入操作可能使已有指针/迭代器失效若算法中途需要写容器要么把“取指针”推迟到写操作全部完成之后要么每次写操作后刷新指针使用TMap迭代器遍历时调用FindOrAdd同理应改用快照复制键集合遍历。v1.0.0结构体类型字段访问的内存泄漏问题描述记录指出这是“结构体字段 cache”优化引入的问题obj.VectorField会有对该字段的 cache但这个字段 js 不在引用了 cache 未清理。即为提升结构体字段访问性能PUERts 对结构体实例.某字段这类访问做了结果缓存避免重复执行 getter 逻辑。缺陷在于缓存的生命周期管理当 JS 侧不再引用该字段或其宿主结构体对象被 GC时C 侧持有的缓存没有随之清理缓存条目持续累积形成泄漏。修复方式官方记录以单独的修复提交commit1460c0d见 bugs.md清理了缓存随对象生命周期释放的路径使字段缓存与 JS 对象引用共同进退。工程启示结构体字段访问在 UE 中不像 UObject 属性那样自带反射缓存体系脚本引擎通常以“包装对象 getter 绑定”方式实现任何一层引入的缓存都必须显式挂钩 V8 的 GC 事件FinalizationRegistry/ 引用清理回调或对象包装的析构逻辑否则高频字段访问场景下泄漏会线性增长。排查此类问题时可在长会话中周期性打印 C 侧缓存表大小观察其是否只增不减。v1.0.0FBox2D 首字段访问失败字段错位问题描述这是“结构体字段 cache”引入的第二个问题且是最容易被业务代码踩到的一个如果一个结构体它的第一个字段也是结构体并且是静态绑定的话目前只看到FBox2D满足这些条件就会出现如下问题。用 TypeScript 复现let box new UE.Box2D(); box.Max.X 100; box.Max.Y 102; box.Min.X 1100; // 其实设置的是 box.X box.Min.Y 102; // 其实设置的是 box.Y期望写入box.Min的值实际写进了box自身的X/Y字段传入引擎后box.Min.X、box.Min.Y都是未初始化值。也就是说字段访问发生了错位——对嵌套结构体字段的缓存命中了错误层级宿主结构体的字段偏移把“首字段的子字段”解析成了“宿主结构体的同名字段”。从FBox2D的结构看Min、Max均为FVector2D而外层类本身也有X、Y语义相近的访问器这类“嵌套结构体首字段 静态绑定”的组合恰好触发了字段缓存键构造时的边界条件。修复方式官方记录以单独的修复提交commit347e57a见 bugs.md修正了字段缓存的层级归属使嵌套结构体字段的读写落到正确的字段偏移上。工程启示这类“看起来写成功了、实际没生效”的静默错误比崩溃更难定位涉及向量、包围盒等嵌套结构体的数值逻辑相机、UI 布局、碰撞盒建议加断言或回读校验字段缓存的键必须完整编码“对象层级 字段名 字段所属类型”任何一层归属错配都会导致跨层串写。小结从缺陷记录看脚本引擎桥接层的质量要点bugs.md 收录的三个缺陷高度聚焦共同指向脚本引擎与 C 运行时桥接层的两类高频风险容器指针/迭代器生命周期TMaprehash 使BindInfoPtr失效修复方式是在每次可能变更表结构的调用FindOrAdd、MakeSureInject后重新Find相关代码可参考 JsEnvImpl.cpp性能缓存的正确性与回收结构体字段缓存既带来了泄漏JS 侧失联后缓存未清理也带来了错位嵌套结构体首字段访问到错误层级任何为“高频小对象访问”做的缓存都必须同时保证键的唯一性与生命周期的可回收性。对使用 PUERts 的开发者而言这些缺陷在当前版本中均已修复适用前提是使用包含上述修复提交的版本对自研或维护类似“引擎 ↔ 脚本”桥接层的团队本文的两个案例可作为容器使用与缓存设计的对照检查清单。【免费下载链接】puertsPUER(普洱) Typescript. Lets write your game in UE or Unity with TypeScript.项目地址: https://gitcode.com/GitHub_Trending/pu/puerts创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表