CD Code Digest Hub /GTA V

脚本 VM、Native 边界与玩法资源生命周期

结合基础项目报告中的 SanScript/Native 调用链,继续分析 RAGE script 与 Framework fwscript。未编译、执行或修改脚本。入口:Summary。

7 分钟阅读 7 个章节 1 张图

1. 脚本不是另一套完整引擎#

脚本表达任务和世界行为,Native 把这些行为连接到原生实体、streaming、物理、UI 和网络。RAGE scrProgram 提供程序资源,scrThread 提供执行状态,Framework scriptHandler 负责脚本拥有的对象/资源以及联网生命周期,游戏 GtaThread 接入产品更新规则。

Native 在原生侧执行,所以脚本引擎只解释少量字节码并不代表这一帧工作很少。一次创建、查询或资源操作可以触发大量原生工作。

2. 代码、字符串、全局和线程栈分离#

scrProgram 包含 opcode pages、Native 表、statics、globals 和 string heap;代码索引用 page shift/mask 选择页和偏移。字符串页 shift 为 14,全局页同样按 shift 14 划分,但后者单位是 scrValue,不是字节。不能把两者都称为固定 16 KiB。

全局分页的源码注释明确希望避免重载时要求一个过大的连续内存块。它减少连续块需求并支持程序资源组织,代价是页表间接访问、编码上限与跨页边界处理。页式布局不意味着操作系统按需分页;实际载入仍由程序资源和 streaming 协议决定。

scrThread 保存 PC、SP、运行/阻塞/中止状态、wait 和 stack 等执行信息。名为 thread 的对象是逻辑执行上下文,不是给每个脚本创建一个 OS 线程。

3. 合作式执行与时间语义#

UpdateAll 按脚本 priority 和线程表遍历,给每个有效线程调用 Update(insnCount)。普通解释循环递减指令配额;Wait 将逻辑线程设为 BLOCKED,在后续更新中按 time step 扣除等待时间,达到零再解除。Unwarped/Unpaused 等变体表达不同游戏时间语义。

这便于脚本以顺序逻辑表达跨帧行为,但任务可预期性依赖显式让出、指令配额以及 Native 调用成本。高优先级只影响遍历次序,不能自动保证脚本工作有一个严格墙钟时限。

特别需要区分配置:SMALL_SCRIPT_CORE 分支转交另一执行器;EXECUTABLE_SCRIPTS 条件下可选择 Exec;非 small 分支有本文件可见的解释循环。不能将该快照的所有目标统称为同一种 VM 执行方式。

SCRIPT_DEBUGGING 下有指令超额 Fault 和超过 1 ms 的 warning;timebar 的 5 ms budget 同样是观测标记。它们不是所有发行配置中自动抢占 Native 的实时调度保证。新项目若需要脚本隔离,应为重 Native 设计异步请求、可取消句柄和单独预算。

4. Native 调用是一份紧凑 ABI#

可见解释路径的 OP_NATIVE 从编码提取 paramCount 和 returnSize,读取 Native 表索引,保存 SP,并以 Info 指向参数区与返回区。程序保存 Native 引用,运行时通过注册映射到 C++ 命令。

这种方式把玩法脚本与具体 C++ 函数地址组织分开,并避免每次按字符串查找。不过签名、参数宽度、返回值布局和对象句柄必须一致。脚本可调用不等于任意线程可调用;原生命令仍需遵守主线程、场景修改和资源发布阶段。

基础项目报告中的车辆例子从声明/注册到 commands_vehicle.cpp 与 GtaThread,展示了这一桥接链。新增 API 时应同时检查脚本声明、注册、原生实现、错误语义和资源归属,避免只完成“函数能被找到”。

5. scriptHandler 把脚本结束变成资源协议#

scriptHandlerObject 提供 OnRegistration/OnUnregistration、Cleanup、script info、net object 和 entity 接口。scriptResource 覆盖模型、粒子、动画等资源,区分脚本分配 ID、底层 reference、Create/Destroy、DetachOnCleanup 和 LeaveForOtherScripts。

脚本结束时资源不必一律删除:有的销毁,有的显式 detach 后持续存在,有的需等待其他脚本不再引用。默认 Detach 并未对所有资源实现,调用者不能假设统一支持。共享资源的底层引用计数与脚本的责任记录也不是同一个计数。

网络脚本对象还包含 HostToken 与对象 ID 等身份。任务失败、会话切换、host 变化或强制中止,都要求脚本资源治理与原生对象生命周期一致。垃圾回收或 VM 栈清空本身无法替代这些跨系统清理。

6. 性能、可维护性与新项目启发#

机制优点限制及改进方向
逻辑线程与 Wait顺序表达跨帧玩法等待理由和唤醒条件应可观测,避免大量轮询
Native 桥接重计算复用原生能力指令数无法度量 Native 成本,应按 Native 分类采样
程序分页降低连续内存需求页尺寸、索引和版本需工具验证
显式资源 handler任务结束可系统性回收Detach/共享/联网对象需要清晰所有权规则
调试栈与 Native 日志定位跨脚本/原生问题发布配置剥离后仍应保留低成本异常计数

新项目可保留轻量 VM 和 Native 的分工,但不宜把所有玩法写进无预算的同步 Native。建议将可能等待 IO、导航或网络的操作建模为显式异步状态,并在脚本终止时取消请求;结果返回时校验脚本/对象世代,防止陈旧回调复活任务。

建议验证(未执行):无 Wait 的循环、单次高成本 Native、脚本退出时资源仍在装载、多个脚本共享模型、暂停/慢动作下 Wait 变体、联网身份变化期间 Cleanup。分别测 VM 指令与 Native 时间,才能区分解释成本和系统成本。

7. 源码证据#

文件入口
program.h31–39 页配置;69–76 代码寻址;91–128 程序与全局接口
thread.h状态、序列化执行上下文、Run/Update
thread.cpp850 执行配置;1147 Native;1940 调试预算;2342/2402 Update;2675 Wait
scripthandler.h39–65 对象契约;71–121 资源清理和保留策略
READMENative 注册和 GTA 脚本接入链

关联:资源管线、网络、AI、性能。