代码批处理器


想要了解什么是批处理器、为什么要使用批处理器,可以看这里。本文介绍的是代码批处理器,命令批处理请看这里


基本用法

批处理器是专供超级用户使用的,用以下方式调用:
> @batchcode path.to.batchcodefile
path.to.batchcmdfile 是以“.py”结尾的批处理文件的路径(当你在游戏中运行该文件时不需要加上它)。路径以python格式给出,是以批处理文件夹为起始的相对路径,批处理文件夹在 settings 文件的 BATCH_IMPORT_PATH 中设置。默认文件夹是 game/gamesrc/world。所以,如果你想运行 game/gamesrc/world/examples/batch_code.py 的批处理文件例子,你可以用
> @batchcommand examples.batch_code
批处理器会试着从头到尾执行整个批处理文件。想要以交互模式控制文件的执行,可以使用 /interactive 参数。参数 /debug 会开启处理器的调试模式。详细信息请阅读下文。


批处理文件

代码批处理文件基本上就是普通的Python源文件。批处理文件与标准Python模块的唯一区别是批处理文件用了一种特殊的语法将内容分割成许多块。分割成块可以让批处理器在执行时拥有更多的控制权,特别是在开启处理器交互模式的时候。在交互模式中,这些代码块可以让批处理器暂停执行,一次只执行一块代码。所以,如果你不想让代码被分开执行,可以把它们放在一个单独的块中。

以下是 .py 批处理文件的语法规则。
  • 以 #HEADER 开始的行标志着头部块的开始。这是为了处理引用模块及其他代码块可能会用到的公共变量。头部的Python代码会插在该文件的所有 #CODE 块前面。你可能会定义多个 #HEADER 块,但这就相当于定义了一个大块内容。在合并代码前,#HEADER块中的注释会被剥离出来。
  • 以 #CODE 开始的行标志着代码块的开始。代码块包含了可执行的Python代码。在运行时,#HEADER 块会加到所有代码块之前。
  • #CODE (信息) 对象1, 对象2, ... 是代码块头部的可选形式。(信息)字段说明了这块代码是派什么用处的,会被批处理器显示出来。对象1, 对象2, ... 部分是可选的对象标签,处于调试模式的批处理器会在执行完代码块后自动将它们删除。
  • 以 #INSERT 路径.文件名 开始的行可以把另一个代码批处理文件的内容加载到本文件中。插入文件中的 #CODE 块会像在本文件中定义的一样被执行,但它们不会使用当前文件 #HEADER 块的内容,他们只使用自己的 #HEADER 块。
  • 新的 #HEADER、#CODE 或 #INSERT 块(或文件末尾)会结束前一个块。第一个块之前的文本会被忽略。
  • 以 # 开头,但不是 #HEADER、#CODE 或 #INSERT 的内容都会被当成注释。
  • 在块的内部,使用普通的Python语法规则。对于缩进,每个块都是单独的Python模块。
  • 在脚本中,变量 caller 总是可用的,它指向批处理命令的调用者。

下面是 game/gamesrc/world/examples/batch_code.py 中的例子。
    #
    # This is an example batch-code build file for Evennia.
    # 这是一个Evennia代码批处理建造文件的例子。
    #

    #HEADER

    # This will be included in all other #CODE blocks
    # 这些内容会被所有 #CODE 块引用

    from src.utils import create, search
    from game.gamesrc.objects.examples import red_button
    from game.gamesrc.objects import baseobjects

    limbo = search.objects(caller, 'Limbo', global_search=True)[0]


    #CODE (create red button)

    red_button = create.create_object(red_button.RedButton, key="Red button",
                                      location=limbo, aliases=["button"])

    # caller points to the one running the script
    # caller 指向脚本的运行者
    caller.msg("A red button was created.")

    # importing more code from another batch-code file
    # 从其它代码批处理文件引入更多代码
    #INSERT examples.batch_code_insert

    #CODE (create table and chair) table, chair

    table = create.create_object(baseobjects.Object, key="Blue Table", location=limbo)
    chair = create.create_object(baseobjects.Object, key="Blue Chair", location=limbo)

    string = "A %s and %s were created. If debug was active, they were deleted again."
    caller.msg(string % (table, chair))
它使用Evennia的Python接口依次创建了三个对象。


调试模式

试着用以下命令运行示例脚本
@batchcode/debug examples.batch_code
批处理脚本会运行到结束,然后告诉你它运行结束了。你会看到按钮和两件家具的创建消息。你环顾四周,可以看到按钮,但你不会看到任何桌子和椅子!这是因为我们在运行时使用了 /debug 参数。处理器的调试模式是给你测试脚本用的,也许你在寻找代码中的bug,或者想看看事务的行为是否和设想的一样。如果反复运行这段脚本,会不断有相同名字的按钮、椅子和桌子创建出来,堆积如山。之后,你不得不花力气专门去删除它们。调试模式会自动删除创建出来的东西,这样一来房间就不会挤满无用的物体了。

第二个 #CODE 块的头部给出了桌子和椅子的变量名,这和之后我们指派给新物体的实际变量名一样。在调试模式下,代码批处理器会寻找这些指定的名字并对它们执行delete()。由于在创建按钮的块中没有指定这样的变量名,批处理器无法帮助我们 —— 这就意味着即使在调试模式下,按钮还是会留下来不被删掉。


交互模式

交互模式的工作方式与命令批处理器非常类似,它可以让你控制批处理文件单步执行。这对调试很有用,还可以用来选择执行特定的块。使用带 /interactive 参数的 @batchcommand 命令可以进入交互模式。
@batchcode/interactive examples.batch_code
你会看到以下内容:
01/02: #CODE (create red button) [...]         (hh for help)
这说明你在第一个 #CODE 块中,是两段批处理命令中的第一段。要注意,此时这段代码还没有实际执行!

想要看将要执行的完整命令,可以使用 ll(批处理版的“look”)。
    from src.utils import create, search
    from game.gamesrc.objects.examples import red_button
    from game.gamesrc.objects import baseobjects

    limbo = search.objects(caller, 'Limbo', global_search=True)[0]

    red_button = create.create_object(red_button.RedButton, key="Red button",
                                      location=limbo, aliases=["button"])

    # caller points to the one running the script
    caller.msg("A red button was created.")
和前面给出的示例代码做个比较。请注意 #HEADER 的内容被贴在 #CODE 块之前了。使用pp实际执行这块代码(这会创建按钮并给你一个消息)。使用nn(下一步)前往下一段命令,使用 hh 获取帮助列表。

如果发生了异常,在批处理文件中修改它们,然后使用 rr 重新加载文件。你会仍然处在之前的命令上,可以根据需要用 pp 命令重新执行它。这构成了一个简单的调试循环。如前面提到的,它可以让你重运行单个出错的命令,这对较大的批处理文件是非常有用的。(同时不要忘了 /debug 模式) 。

使用 nn 和 bb (next和back)可以在文件中移动,例如 nn 12 会向前跳12步(不会执行其中的任何命令)。在交互模式下,Evennia的所有普通命令同样可以工作。


限制和注意事项

到目前为止,代码批处理器是在Evennia中构建世界的最灵活的方式,但有一些事项你需要注意。
  • 安全性,或确切地说是缺乏安全性。默认只允许超级用户使用代码批处理器是有原因的,代码处理器在运行时不受Evennia的任何安全检查,并且可以完全访问Python。如果让别有用心的人使用代码批处理器,他就可以在机器上执行任意的Python代码,这有非常大的危险。如果你想允许其他用户使用代码批处理器,你应该确保Evennia作为单独的、非常受限的用户在你的机器上运行(比如在“沙盒”中)。相比之下,命令批处理器要安全得多,因为用户只能在游戏“内部”运行它,无法做出超越游戏命令许可的事。
  • 你无法在代码块之间通信。全局变量无法在代码批处理文件中工作,每个块都是一个独立的运行环境。同样的,你不能在一个 #CODE 块中给 #HEADER 中的变量赋值,然后期望另一个 #CODE 块能够读到变量的变化(这是python执行的限制,如果允许这么做会导致在交互模式中代码难以调试)。这带来的主要问题是,比如你在一个代码块中建立了房间,然后想让另一个代码块中的房间与它连接起来,要做到这点,你必须在数据库中搜索你所创建的房间的名字(因为你无法提前知道分配给它的数据库号) 。这听起来挺麻烦的,不过有个更简单的解决办法 —— 使用对象别名。你可以给任何对象指定任意数量的别名,确保其中一个别名是全局唯一的(比如“room56”),此后你就总能找到它了,不管它的主名字是否和其他代码块中成百上千的房间重复(巧合的是,如果你想将房间组合起来,这也是实现“区域”的一种方法)。