帧调度、协作式并发、取消与生命周期
1. 游戏在哪里运行脚本#
CGame 在不同游戏更新路径注册 CTheScripts::Process;后者进入 InternalProcess,更新脚本 handler、相关游戏脚本辅助系统,再调用 scrThread::UpdateAll(s_InstructionLimit)。[S1]
UpdateAll 按最高、普通、最低优先级遍历有效线程,逐个调用 Update;手动更新优先级不在这一普通遍历范围。GtaThread::Update 先处理暂停和强制清理、设置当前脚本环境,再调用基类 VM 更新。[S2]
由这条路径可确认脚本实例是由游戏更新驱动的虚拟线程,不是每实例一个 OS 线程。它们的协作式运行不能反向推出整个游戏单线程:Native 可能提交异步引擎任务,网络、流式加载和渲染有自己的调度。
2. WAIT 的完整时序#
WAIT 初次调用时始终进入 BLOCKED,连 WAIT(0) 也如此;下次执行才检查剩余时间并解除阻塞。[S3] 它不是忙等,也不是 Win32 Sleep;所等待的时间基于引擎提供的 timestep,可能受暂停/时间缩放影响,代码还提供 unwarped/unpaused 变体。
普通脚本调用帧会在挂起期间保留,所以函数内部 WAIT 之后仍可访问局部变量。代价是整个实例的栈仍被占用,引用数据必须保持有效,且被调用函数是否挂起从签名上不显式可见。
3. 指令预算必须分配置解释#
当前 DEFAULT_INSTRUCTION_COUNT 为 13,000,000。UpdateAll(0) 使用这个默认值,并把同一预算值分别传给各个线程;不是全游戏脚本共享一个不断扣减的总配额。[S2]
普通可调试内核逐指令扣减 insnCount,超限会进入 SCRIPT_DEBUGGING 下的 fault 逻辑。快速内核的签名没有外部 insnCount 参数;其内部十亿次计数保护仅在 !__FINAL && !THREADED_INTERPRETER 条件下存在。[S4]
因此不能保证“发布版每线程 1,300 万指令就自动公平让出”。也不能使用旧手册的 1,000 指令描述当前工程。正确结论是:玩法必须主动提供挂起点,预算和故障检测在具体内核/宏下另行核对。
即使 VM 有指令上限,一条昂贵 Native 仍可能花很长墙钟时间;指令数限制也不自动约束它生成的 AI、物理、流式或网络后续工作。
4. 并发中的共享状态#
GLOBALS 在脚本间共享。顺序更新减少了同时写同一 CPU 内存的某些竞态,但不会消除时序问题:某脚本读取状态后 WAIT,其他脚本或引擎可在间隙改变状态;恢复时应重新验证句柄、任务阶段和网络权限。
危险推理:
读到“目标有效” → WAIT → 仍按目标有效继续操作
稳妥逻辑:
等待完成 → 再验证目标/权限 → 执行下一阶段这类问题属于跨帧检查与使用之间的失效,不需要真正多核同时执行就会发生。一个大共享结构体也不是自动事务;多字段变更与读取需要约定更新顺序或版本/状态标志。
5. 创建、终止与资源归属#
生产代码通常执行 REQUEST_SCRIPT → 循环 HAS_SCRIPT_LOADED + WAIT → START_NEW_SCRIPT → SET_SCRIPT_AS_NO_LONGER_NEEDED。最后一个调用释放请求方对程序资源的需要,不表示刚启动的实例立即被销毁;运行实例与资源引用共同决定可卸载时机。[S5]
GtaStartNewThreadWithProgramId 创建线程并将其注册给 script handler。线程退出与资源清理由 GtaThread 和 handler 组织,后台脚本还存在退出标志应答等额外约定。这不是简单“调用 END 就释放所有游戏世界影响”。
6. 强制清理与非局部取消#
HAS_FORCE_CLEANUP_OCCURRED 对应的游戏机制保存单独的 PC/FP/SP;例如玩家死亡/被捕、单机转多人或外部触发时,DoForceCleanupCheck 恢复该位置、清除阻塞状态,进入脚本清理分支。[S6]
这比让每层函数手工返回状态更便于任务中断,但不是结构化异常栈展开。一个清理入口必须覆盖已经做出的副作用:角色/车辆、HUD、摄像机、玩家控制、任务标志和注册的网络数据。资源自动跟踪能兜底一部分,不能替代所有玩法复位。
7. 对新语言的建议#
建议采用可见的挂起效果与结构化任务:父任务持有子任务、取消向下传播、清理作用域提供确定的结束钩子。区分游戏时间、真实时间和暂停无关时间,避免把所有等待都压成一个整数毫秒 API。
调度器同时记录 VM fuel、Native 耗时和脚本总墙钟,所有发布模式保留一致的最低保护。对预算耗尽,应明确是下帧续跑、任务失败还是诊断;自动抢占点还要遵守不允许中断的引擎登记区间。
源码证据#
- S1 — CGame 注册脚本 Process;InternalProcess。
- S2 — UpdateAll;默认预算;GtaThread::Update。
- S3 — Wait。
- S4 — 普通内核超限;快速内核计数器。
- S5 — main.sc 资源请求与启动;线程注册。
- S6 — force cleanup。