快照和回滚
由于YRE的所有游戏状态(画面、音乐、脚本解析情况等)都是可序列化描述子结构,因此对于某个时刻进行快照动作对YRE而言是很容易实现的。
相比提供简单的文本回滚,YRE提供的回滚功能更加强大,它的效果是将整个游戏状态回退到快照的时刻。任何可存档上下文都可以被回滚,包括画音状态、场景上下文、全局变量等,即用户甚至可以通过回滚来撤销在之前的时刻里做出的选择项分支并重新选择而不需要反复存读档。但需要注意的是,持久性上下文不被回滚所影响,因为一个达成的成就、解锁的CG不应该因为回滚而被撤销。
状态的快照
可回滚快照RollbackableSnapshot
是YRE进行状态记录的容器对象,它会记录以下的运行时状态:
属性名 | 作用 |
---|---|
VMRef | 快照时刻主调用堆栈的拷贝分支 |
ScreenStateRef | 快照时刻屏幕状态的拷贝分支 |
SemaphoreDict | 快照时刻信号绑定字典的拷贝分支 |
globalDao | 快照时刻全局变量上下文的拷贝分支 |
sceneDao | 快照时刻场景上下文的拷贝分支 |
ReactionRef | 快照时刻重现动作的拷贝分支 |
MusicRef | 快照时刻音乐状态的拷贝分支 |
TimeStamp | 快照时间戳 |
可分支状态容器
YRE为可进行拷贝分支的状态描述子容器定义了公共基类ForkableState
,它的派生类都可以调用继承自它的Fork
方法来产生一个深拷贝分支。运行时信息管理器RuntimeManager
、符号表管理器SymbolTable
和屏幕状态管理器ScreenManager
都是它的派生类。
分支的深拷贝是使用序列化-反序列化来完成的。但需要注意的是各描述子容器中的描述子在做深拷贝时,采用的是更高效的反射机制实现的,关于这部分内容将在画音渲染章节中讨论。
快照时机和过程
定义游戏的安全点指主调用堆栈上处于一个稳定状态,这个稳定状态可能是一次对话显示完毕正在等待用户的点击继续,也可能是等待用户点选选择项,此时游戏的状态是稳定不变的。当游戏状态从一个安全点到达另一个安全点时,YRE会拍下前一个状态的快照,并压入前进状态栈中。前进状态栈的底层数据结构是一个双端链表,之所以使用这种结构是因为它既可以很好的模拟栈的LIFO特性,又可以很高效地剔除掉最早进入的元素,以控制整个状态栈的尺寸不要超过YRE所规定的最大回退步数。
在进行状态快照拍摄时,回滚管理器RollbackManager
将按照以下逻辑顺序进行快照:
- 调用总控制器
Director
的PauseUpdateContext
方法暂停消息循环 - 调用动画控制器
SpriteAnimation
的ClearAnimateWaitingDict
方法强制结束全部动画 - 检查要回滚的上一状态和当前状态是不是处于不同的场景,如果是,设置一个标记位F,并停下并行处理
- 在前进状态栈中弹出栈顶,将弹出的可回滚快照对象中各个游戏状态描述子容器的拷贝分支替换到游戏的主分支上
- 重新绑定主渲染器上的调用堆栈,重绘整个画面,恢复音乐,并向调用堆栈提交一个状态复现中断,来复现快照时刻正在演绎的动作
- 检查标记位F,如果被设置,说明本次回滚是跨场景的,那么就重新启动这个回滚目标场景的并行处理,并恢复它的信号分发绑定
- 最后启动主调用堆栈上的消息循环
需要注意的是,在游戏存档时,快照并不会被保存。也就是说,从存档恢复的游戏是不能回滚的,因为此时前进状态栈是空的,因为它在游戏保存的时候已经被临时清空了。