Verse Wiki — 写给蓝图作者的 Verse 手册
拓展 · EXTRA

效果系统与事务回滚:no_rollback 全解

「This invocation calls a function that has the no_rollback effect...」——几乎每个 Verse 新手都会撞上的第一条天书报红。本页把它拆到原子级:什么是效果系统、那种「可能反悔」的地方为什么像一台时光机、以及把逻辑改对的两种姿势。

一、报错现场还原

场景通常长这样:你想在一个 Branch(if)判断里顺手调用某个函数——可能是引擎自带的某个设备节点(论坛上的经典案例是一个不带 transacts 的 GetTags() 调用),也可能是你自己随手搭的小函数——然后 Compile 甩来这句:

编译器输出
This invocation calls a function that has the 'no_rollback' effect,
which is not allowed by its context.

字面意思是:「这次调用的函数带 no_rollback 效果,而它所在的地方不允许。」翻译成人话:你把一个「做了就收不回」的函数,塞进了一个「随时可能要反悔、要整体撤销」的地方。要看懂这句话,得先认识 Verse 的效果系统。

二、失败上下文是一台「时光机」

第 22 课讲过:那种「可能走不通」的地方(Branch 的判断、<decides> 函数体等)里的一切都是推测执行——先在影子图上记账再动手,一旦某一步宣布「没过」,账本一撕,所有改动整体撤回,像什么都没发生过。这不是比喻:Verse 真的把「先试连、能过才落地、不过就当没连」做进了语言里(相当于一整套自动回滚的事务)。每个进入这种地方的写动作都被记在案,没过就撤、过了才提交。decides 函数走不通后「像什么都没发生」的干净利落,靠的就是这台机器。

但时光机有一个硬性前提:舱里的每件事都必须能倒回去。改一个变量(Set 一下)?能,再 Set 回来就行。造一个对象?能,丢掉就行。这些正是 <transacts> 承诺的范围——读、改、造这几种事它都兜得住,全部记在账本上。可是往屏幕上用 Print String 打一行字呢?玩家已经看见了,收不回来。让引擎放一个音效、真开一扇门呢?世界已经变了。这类「外面看得见的动作」没法倒回去——所以 Print String 这类操作进不了那种「可能反悔」的地方。

关键在默认值:一个什么效果徽章都不戴的函数,默认拿到的就是 no_rollback。Compile 不知道它内部干没干「收不回的事」,只能按最坏情况处理——一律拒之门外。于是你随手搭的无辜小函数,一进 Branch 判断就报红。

三、把代码改对的两种姿势

知道了原理,修法就顺理成章:要么别把它带进时光机,要么向 Compile 证明它能倒回去。两种姿势各来一遍:

combo_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

combo_device := class(creative_device):

    var Hits:int = 0

    # 没有效果标注 → 默认 no_rollback(它含 Print,本来也回滚不了)
    AddHitAndReport():int =
        set Hits += 1
        Print("Hits: {Hits}")
        Hits

    # 只读写内部状态 → 可以承诺 transacts
    AddHit()<transacts>:int =
        set Hits += 1
        Hits

    OnBegin<override>()<suspends>:void =
        # if (AddHitAndReport() > 3):   # 编译报错:no_rollback 进了失败上下文
        Current := AddHitAndReport()
        if (Current > 3):
            Print("姿势一:先在外面调用存进常量,条件里只用常量")
        if (AddHit() > 3):
            Print("姿势二:transacts 函数可以直接进失败上下文")

姿势一(挪出去):在那种「可能反悔」的地方外面先把函数调用跑掉,结果存进一个常量,判断里只比较这个常量。读一个常量当然能倒回去,Compile 放行。这一招对你改不了的引擎自带节点尤其管用——它不带 transacts,你就先取到值、再拿值去判断。

姿势二(戴徽章):如果这个函数是你自己搭的、而且里面只是改改变量、造造对象,直接给它戴上 <transacts>。注意这是一句承诺、不是装饰:一旦戴了,函数体里就不能再有 Print String 这类收不回的动作,否则 Compile 会直接在这个函数定义的地方拦下你。

最后从设计角度看一眼:Epic 为什么把门槛设得这么严?因为 Verse 的「可能走不通」机制(decides、Branch 判断、可能没过的表达式)是整个语言的地基,而「没过就当没发生」这句话,只有在一切外部动作都可控时才站得住。效果系统就是把「这个函数会对世界做什么」写进它的名字旁边、交给 Compile 逐条审的机制——你在一个函数的签名上看到的每一枚效果徽章,都是一份 Compile 会强制执行的合同。

为什么 Print String 不能出现在「可能反悔」的地方里?

一个不标任何效果徽章的函数,默认效果是什么?

本文整理自 Epic 开发者论坛与官方文档:论坛:This invocation calls a function that has the no_rollback effect ↗论坛:no_rollback effect struggle ↗官方文档:transacts ↗