效果系统与事务回滚: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 证明它能倒回去。两种姿势各来一遍:
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 ↗。