CD Code Digest Hub /GTA V

面向新游戏脚本语言的设计借鉴与路线

8 分钟阅读 10 个章节 1 张图

本篇是基于前述源码分析提出的设计建议,不是 SanScript 的功能说明,也不是已实现的新语言。目标是把取舍变成可以验证的设计决策。

1. 先选使用情境,再选语法#

问题内部受信内容团队面向外部 UGC
首要成本迭代、调试、引擎集成、内容规模同样重要,另需强隔离、资源计量和版本治理
Native 能力可广,但仍需契约和误用诊断按能力授权,验证输入与配额
执行预算保证帧时间与定位失控逻辑发布版必须保留可执行的硬边界
数据模型可接受较多引擎特有句柄不应暴露宿主地址和不安全泛型内存
更新策略可先采用重启实例需额外管理已发布内容及旧版本兼容

两种场景都需要任务取消、资源归属、可观测性和清晰的网络权威模型。不要因为内部可信就省去生命周期设计,也不要因 UGC 需求把全部隔离问题压给语法检查。

2. 值得保留的核心#

SanScript 的优势不是关键词简短,而是将小型语言、可预测值布局、跨帧执行、引擎命令和专用工具组合起来:

  • 显式类型与领域句柄帮助作者在编辑时发现对象混用。
  • VECTOR、定时、资源请求等表达接近游戏问题。
  • 离线编译和紧凑 VM 让内容与原生构建分离。
  • 保留调用栈的协作式等待使顺序任务易于编写。
  • 资源 handler、清理入口、调试器和 profiler 共同支撑生产。

这些结论分别来自类型报告、调度报告、Native 报告与开发体验报告。

3. 建议形成的设计决策#

决策建议起点原因与代价
类型显式静态类型,局部推导可选;严格枚举/句柄保持 API 清晰;需要处理错误结果和可空性
代码组织显式 module/import/export,实例状态单独声明限制 include 扇出;增加模块链接与版本管理
执行首版小型字节码 VM + 参考解释器易调试、可移植;后续按数据评估 AOT/JIT
挂起签名标记 may-yield 或 async,调用点用 await暴露跨帧窗口;作者需理解任务取消
资源task scope 持有句柄,显式 transfer/detach自动清理更可靠;需要定义共享资源归属
数值固定宽度与确定的溢出/除零规则可测可移植;与 C++ 默认行为可能不同
字符串明确 UTF-8 字节/字符操作、只读与可写区分避免容量误解;需单独处理本地化文本
聚合数据小值内联,大只读表资源化;可选受限 arena内存可预测;容器容量与寿命更显式
FFI一个 schema 生成声明、包装、校验与文档减少漂移;schema 维护是必要成本
失败Result/错误枚举 + 独立取消;确定性清理区分可恢复错误和 bug;不能全部用异常掩盖
持久化/复制版本化 schema,与 VM 槽布局分离跨平台、更新更稳;要写迁移策略
沙箱装载验证 + 运行 fuel + 权限 NativeUGC 必需;会有检查成本

这里没有通用最佳答案。例如 GC 可以简化复杂对象图,arena/值模型更利于预算;应按目标内容是否需要长寿命图结构选择,而不是因 SanScript 没有通用 GC 就否定 GC。

4. 一个建议的语义草案#

下面是新语言设计伪代码,不是可编译的 SanScript:

text
task Patrol(ctx: MissionContext) -> Result<Unit, MissionError> {
    let model = await ctx.assets.load_vehicle("police", timeout = 5s)?
    let vehicle = ctx.entities.spawn_vehicle(model, ctx.spawn_point)?
    defer vehicle.release_to_world()

    while ctx.is_active() {
        await ctx.next_frame()
        await ctx.authority.require_control(vehicle)?
        let live_vehicle = vehicle.try_get().ok_or(EntityGone)?
        ctx.ai.update_patrol(live_vehicle)
    }
    return Ok()
}

要解决的不是这段语法是否漂亮,而是明确这些契约:load 可失败/超时/取消;模型 lease 的寿命明确;spawn 返回可失效句柄;defer 在正常返回和取消时执行;await 后对象和权威需要重验证;控制权申请不会无限持有引擎主线程。

若游戏允许无脚本主机的服务器权威模式,可替换 ctx.authority 的实现,而不用让所有业务逻辑依赖某台客户端永远是 host。

5. 不应直接照搬的机制#

现有机制需要避免的后果建议替代
shortcircuit 编译开关同一源程序求值副作用随配置变化语言规范固定短路规则
多套 parser/手册编辑器接受而编译器拒绝共享前端及语义测试
GLOBALS 固定偏移模块重构引发隐性 ABI 错位模块化状态、稳定字段 ID
指针宽度影响槽布局内存/保存/网络单位混淆执行布局与序列化分离
隐式可挂起 Native同帧契约被深层函数破坏effect 检查与非挂起区间
单层 CATCH/force cleanup跨层跳转依赖手工复位清单结构化取消与作用域资源
快速内核弱化预算发布环境失控循环不可控所有内核一致的保护语义
类型/函数身份仅依赖隐式上下文回调跨模块/热更新悬空模块版本化函数引用

这些并非否定当时的工程约束;它们是长周期项目中已经能从源码看到的维护成本。

6. 反射、泛型、OOP 与元编程应怎样取舍#

当前 SanScript 的结构体、引用参数、函数指针、Native 子类型和共享头库已能完成大型玩法。新语言不必为了“像通用语言”一次加入完整类继承、模板系统、宏、动态 eval 和反射。

更直接的需求是:能否表达只读数据、可失败查询、资源所有权、跨帧任务、类型安全事件和稳定保存字段。可先提供有限泛型容器、代数数据类型/模式匹配、schema 派生和显式接口;宏若需要,应保持源码映射和可诊断性。

动态 eval 会扩大运行期编译、安全和可重现性问题;反射可先限制在编辑器与序列化元数据。面向对象可用组合与接口满足多数 API 组织需求,是否加入继承应由实际内容样例决定。

7. 建议的系统结构#

关键原则是把“运行是否正确”“资源是否受控”“作者是否看得懂”放在同一套信息模型上。效果、源位置、类型和模块身份不能在生成代码时全丢掉,再由工具猜回来。

8. 分阶段落地与验收#

阶段产出进入下一阶段的条件
语义原型规范、参考解释器、小样例语料数值/短路/作用域/错误规则可测试,无依赖含糊行为
首版执行链编译器、验证器、VM、源映射、Native 生成同一用例在参考实现和 VM 结果一致;坏产物被拒绝
游戏任务闭环模型加载、实体创建、等待、取消、清理正常完成/失败/取消/实体失效都无遗留资源
作者工具IDE、调试器、profiler、内存报告能定位具体脚本行、Native 和所属资源,不靠宿主崩溃猜测
内容规模验证模块缓存、共享只读数据、压力场景编译与运行成本在项目自行设定的预算内,记录尾部延迟
长周期能力存档迁移、复制 schema、版本更新旧数据/旧模块的兼容结果明确,迁移失败可诊断

验收预算应根据目标平台和场景先定,再测量;这里不凭空给“所有游戏都应小于某毫秒”的数值。关键测试矩阵见调试测试报告,测量方法见性能报告。

9. 最有价值的原型用例#

用本项目五个案例重建最小版本:常驻低频控制器、可失败的剧情任务、可重试存档请求、可迁移网络控制器、可配置开发测试场景。它们能同时压测语法、类型、挂起、资源、权限、工具和版本演化。

如果新语言只能把任务脚本写短,却无法解释为什么卡在 WAIT、哪个 Native 产生大开销、取消后谁释放资源,那么它还没有获得 SanScript 这套系统最值得借鉴的能力。

主要依据#