查看: 2546|回复: 3

[原创发布] 如果你使用【自动存档】时【并行事件】出现问题,请看此帖

[复制链接]
梦石
0
星屑
9687
在线时间
3224 小时
注册时间
2011-3-24
回帖
616

开拓者

发表于 2021-10-26 23:18:10 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 Mr.Jin 于 2021-10-26 23:41 编辑

Project1.zip (1.32 MB, 下载次数: 4)

首先请用该工程执行以下操作:

New Game 进入一盘新游戏,然后和 NPC 对话
当他讲到 Please Exit 时,退出游戏
重新打开游戏,读取刚才对话时生成的自动存档,你会发现并行事件的执行出了问题——

复现到此为止,出现该问题的原因是,在我们进行一个地图事件执行的过程中,其它并行事件也在处理,且可能并没处理完——
在这种情况下,直接调用 DataManager.save_game 方法进行存档,就会将这些并未处理完的并行事件(尤其是不含显示文章、等待这种会阻塞处理的空转事件列)状态以错误的方式进行存储,导致再次读取数据时,执行到错误的条目,进而可能进入错误的分支,引发十分隐晦难解的错误

相信这类问题会经常发生,但可能被误诊为事件本身相关的条件变量和开关问题却找不到解决方法,只能不了了之;所以特将此类问题的源头公布出来,让大家更易捉虫

============== 常用解决方案 ================

上策:自己编写一个方法,在自动存档时把请求存到 $game_temp 或是什么地方去,然后在所有事件处理完的时机再进行实际的 DataManager.save_game
下策:在会出问题的并行事件最顶部垫一条"等待 1 帧",可以避开问题,但是极为不推荐

评分

参与人数 1星屑 +300 赞 +1 收起 理由
alexncf125 + 300 + 1 精品文章

查看全部评分

>>→Lanza Games 2020←<<
Lanza Schneider
浪子心,孤星梦永远流浪……
~不会放弃,我的精力永远充沛~
>>>>>Lanza Adventure<<<<<
>>>>>Lanza的新年派对!<<<<<
>>>>>Lanza的口袋心脏病!<<<<<
梦石
0
星屑
17038
在线时间
6058 小时
注册时间
2011-7-18
回帖
152

开拓者

发表于 2021-10-30 02:46:23 | 显示全部楼层
本帖最后由 gqxastg 于 2021-10-30 09:16 编辑

研究了一下...
确实光修是修不好的,还是需要个人注意用法
(其实说真的并行处理的事件还是应该套一层循环,每次执行完会空一帧太伤了)
试图修正[pre lang="ruby"]# 事件解释器存储的时候@index + 1,原意是事件中使用“打开存档画面”等存档操作时,
# 为了避免读档后会再执行一次。
# 而没考虑到某些指令的脚本选项和其他并行处理的事件解释器,都会被错误的推后一个指令
# 正确解法应该是:除了调用存档操作的解释器推后外,其他(大多)不推后
# 考虑到变量操作里也有用脚本的选项,所以也算上推后
# 而条件分歧推后会直接到成立时的指令,且未经判断则算作不成立,导致两种处理均会执行
# 故不推后(当然暴力点可以所有指令均不推后,代价就是读档后会再存一次档之类的)
# 设置移动路径因为会保存在角色/事件里,如果在等待移动结束时再次执行会重置路径,
# 导致移动紊乱,所以如果已设置路径则必须推后
# 剩下这些是带wait的,而且多半是依据现有变量数值的渐变,重设也会打乱效果
class Game_Interpreter
  # 打开菜单画面, 打开存档画面, 脚本, 设置移动路径
  # 淡出画面, 淡入画面, 更改画面色调, 画面闪烁, 画面震动, 移动图片, 更改图片色调,
  # 设置天气
  [351, 352, 355, 205,
   221, 222, 223, 224, 225, 232, 234, 236].each do |i|
    new_name = "fixindexold_" + (name = "command_#{i}")
    alias_method new_name, name
    define_method(name) do
      @dump_skip_index = true
      send(new_name)
      @dump_skip_index = false
    end
  end
  #--------------------------------------------------------------------------
  # ● 变量操作
  #--------------------------------------------------------------------------
  alias fixindexold_command_122 command_122
  def command_122
    @dump_skip_index = true if @params[3] == 4 # 脚本
    fixindexold_command_122
    @dump_skip_index = false
  end if true # 将这里的true改为false则变量操作所有情况都不推后
  #--------------------------------------------------------------------------
  # ● 储存实例
  #--------------------------------------------------------------------------
  alias fixindexold_marshal_dump marshal_dump
  def marshal_dump
    return fixindexold_marshal_dump unless @list && @list[@index]
    old_index = @index
    @index = old_index - 1 if !@dump_skip_index
    ret = fixindexold_marshal_dump # 原本为@index + 1
    @index = old_index
    ret
  end
end[/pre]
结果仔细想了一下,并行处理错误存储的根源是指令根本没执行
一旦执行了不是瞬间完成就是在等待结束,而等待结束的也基本不适合重设(还是有等待和显示动画之类的不适合跳)
所以还是可以只限制未执行的不推后,剩下执行中的统统跳过
(至于直接在条件分歧里save_game本身就应该避免,可以不管)
暴力修正[pre lang="ruby"]class Game_Interpreter
  #--------------------------------------------------------------------------
  # ● 执行事件指令
  #--------------------------------------------------------------------------
  alias fixindexold_execute_command execute_command
  def execute_command
    @dump_skip_index = true
    fixindexold_execute_command
    @dump_skip_index = false
  end
  #--------------------------------------------------------------------------
  # ● 储存实例
  #--------------------------------------------------------------------------
  alias fixindexold_marshal_dump marshal_dump
  def marshal_dump
    return fixindexold_marshal_dump unless @list && @list[@index]
    old_index = @index
    @index = old_index - 1 if !@dump_skip_index
    ret = fixindexold_marshal_dump # 原本为@index + 1
    @index = old_index
    ret
  end
end[/pre]
所以最后结合这两点,只有在执行指令时且排除特定几个指令之外才跳过就可以了
(当然可能有点偏离主题了,排除指令纯粹是附加的)
最终修正[pre lang="ruby"]class Game_Interpreter
  # 条件分歧, 显示动画, 显示心情图标, 集合队伍成员, 等待
  [111, 212, 213, 217, 230].each do |i|
    new_name = "fixindexold_" + (name = "command_#{i}")
    alias_method new_name, name
    define_method(name) do
      old = @dump_skip_index; @dump_skip_index = false
      send(new_name)
      @dump_skip_index = old
    end
  end
  #--------------------------------------------------------------------------
  # ● 执行事件指令
  #--------------------------------------------------------------------------
  alias fixindexold_execute_command execute_command
  def execute_command
    @dump_skip_index = true
    fixindexold_execute_command
    @dump_skip_index = false
  end
  #--------------------------------------------------------------------------
  # ● 储存实例
  #--------------------------------------------------------------------------
  alias fixindexold_marshal_dump marshal_dump
  def marshal_dump
    return fixindexold_marshal_dump unless @list && @list[@index]
    old_index = @index
    @index = old_index - 1 if !@dump_skip_index
    ret = fixindexold_marshal_dump # 原本为@index + 1
    @index = old_index
    ret
  end
end[/pre]
1.png 2.png 3.png

评分

参与人数 1赞 +1 收起 理由
Mr.Jin + 1 我很赞同

查看全部评分

这里岚风·雷,任饭、PM理性粉、UT/DR原作粉、(Trans)Furry自萌,半吊子技术一枚_(:з」∠)_    游戏制作交流工(liao)作(tian)室欢迎来玩!
【无偿/有偿】RGSS3(VA)脚本定制 + 合作招募/同好交友    修正Firefox/火狐浏览器的代码框复制问题(油猴脚本)
Click→←Click
回复 1 0

使用道具 举报

梦石
0
星屑
9687
在线时间
3224 小时
注册时间
2011-3-24
回帖
616

开拓者

 楼主| 发表于 2021-10-30 09:20:17 | 显示全部楼层
gqxastg 发表于 2021-10-30 02:46
研究了一下...
确实光修是修不好的,还是需要个人注意用法
(其实说真的并行处理的事件还是应该套一层循环 ...

其实推荐的做法还是做一个新的指令来自动存档:
也就是先设一个要存档的 flag,但并不在事件处理中直接执行存档,而是在所有事件处理完后检查该 flag,进行存档并削除该 flag

点评

嗯只是顺着思路修了一下残留问题.正规做法当然是等主解释器不运行时再存档,不过还比较难把控其他并行事件不运行.设计上还是得避免并行里有等待  发表于 2021-10-30 09:25
>>→Lanza Games 2020←<<
Lanza Schneider
浪子心,孤星梦永远流浪……
~不会放弃,我的精力永远充沛~
>>>>>Lanza Adventure<<<<<
>>>>>Lanza的新年派对!<<<<<
>>>>>Lanza的口袋心脏病!<<<<<
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部