Skip to content
云风 edited this page May 8, 2024 · 7 revisions

Ant 程序

如果用默认编译脚本编译引擎,会在 bin 目录下找到编译好的执行文件 ant.exe ,根据编译器和编译选项不同,会放在不同子目录下。例如,使用 mingw 编译的 release 版,位于 bin/mingw/release/ant.exe , vc 编译的 debug 版位于 bin/msvc/debug/ant.exe

这个程序实际是标准 Lua 5.4 并打包有 Ant 相关的 C 模块。运行这个二进制程序,会加载同一目录下的 main.lua 并运行它。

注:bin 目录下的 main.lua 是从 runtime/common/main.lua 由构建脚本复制过去的,所以不要直接修改它,而应该修改源文件并运行 luamake 复制。构建脚本位于 runtime/make.lua

Ant 程序在启动时,加载 main.lua 之前会加载所有的相关 C 模块。C 模块的入口全部列在 runtime/common/modules.c 中,如果游戏需要链接额外的 C 模块,需要修改构建脚本,或采用标准 Lua 可以接受的动态链接库的形式。modules.c 中的 ant_loadmodules() 函数最后会初始化 VFS 模块。这会改变标准 Lua 所带的 IO 函数的行为。

VFS 由 C 和 Lua 混合实现,C 入口在 clibs/vfs/vfs.cpp 。Lua 代码以字符串形式嵌入在源代码中,我们可以看到它替换了 dofile/loadfile/require 等行为,以支持 Ant 的文件系统。

本地模式和运行模式

Ant 的文件系统可以放在本地,也可以通过网络映射。我们把通过网络映射的虚拟文件系统称为 RUNTIME 模式,在 PC 上启动引擎,默认使用本地模式,但可以通过命令行参数 -rt 切换为运行模式(实现见 clibs/vfs/vfs.cpp)。在手机上运行,一定处于运行模式。

运行模式

在运行模式下,所有文件请求都会通过网络发往文件服务器,或使用本地缓存。本地缓存可以提前打包成 zip ,也可以是上一次和文件服务器同步的结果。如果缓存中缺少文件,会产生一个网络请求到文件服务器。注:连接文件服务器只会在程序启动时尝试一次,一旦无法连接,便会进入离线模式,只使用本地缓存。

文件服务器本身也是一个 Ant 引擎驱动的程序,其启动入口在 tools/fileserver/main.lua。通常,我们必须用本地模式启动文件服务器。

由于运行模式本身需要额外的代码实现客户端协议和文件服务器对接。这些代码也是用 Lua 实现,但无法放在本地系统中。所以,构建脚本会把相关 Lua 的源代码编译在执行文件里。这些相关代码位于 engine/firmware 下。虽然它们都是 lua 文件,但一旦修改它们,必须重新运行 luamake 构建,把这些文本编译进引擎的执行文件中。

运行模式下工作,不再依赖其它本地文件。

本地模式

日常 PC 开发推荐使用本地模式,它从本地文件系统中加载文件。但由于 VFS 还承担了 Asset 的自动构建工作,所以在同一个项目目录下同时运行多个程序时,会产生冲突。本地模式不运行在同一个项目目录下同时运行多份。

如果需要启动多份游戏,可以在项目目录下以本地模式启动一份文件服务器。然后以运行模式启动游戏则不受限制(甚至可以不在同一台机器上)。

编辑器模式

Ant 引擎为编辑器做了一些特殊化处理。本地模式下,如果项目名为 editor,就会变成编辑器模式。例如,以下启动方式会进入编辑器模式:

ant.exe tools/editor/main.lua test/simple/

编辑器模式相对于本地模式的特殊之处有:

  1. 定义了全局变量__ANT_EDITOR__为arg[1],也就是第一个参数
  2. 将__ANT_EDITOR__指向的目录下的.mount也会被加载
  3. 资源编译不会缓存结果(资源编译后被修改,再次加载时,本地模式不会重新编译,编辑器模式则会)
  4. 可以读取资源的原始文件,而本地模式读取资源的原始文件会失败(因为已经被视为目录)

上面的启动实例中,会设置有:

ANT_EDITOR=test/simple/

同时,tools/editor/test/simple/ 两个目录下的 .mount 都会被加载。

本地模式和运行模式的区别

本地模式多用于开发。本地模式直接从本地文件系统读取文件,会根据需要调用外部工具编译 Asset 。本地模式中,引擎相关的 Lua 文件必须放在正确的路径中。

运行模式多用于发布。运行模式只使用一个二进制执行文件即可运行,通过 VFS 读取游戏以及引擎的其它文件。运行模式自身无法编译 Asset ,它直接从 fileserver 或本地缓存中读取编译转换后的数据。可以通过打包模块将缓存中的数据打包为 zip 包,跟随引擎的二进制执行文件一起发布。

引擎的初始化

如前文所述,和执行文件同目录的 main.lua (源码在 runtime/common/main.lua) 是引擎在本地模式下运行的第一个 Lua 程序。它会加载 engine/console/bootstrap.lua 并将命令行传入的 lua 程序名转发给它。而在运行模式下,不依赖额外的 main.lua ,直接加载已被编译进执行文件的 engine/firmware/bootstrap.lua

如果运行在本地模式下,我们必须保证当前目录的位置,让 engine/console/bootstrap.lua 是合法的路径。你可以额外编写 shell 脚本在启动执行文件前把进程的当前路径切换到正确的路径下,这通常相对于执行文件的目录 ../../../ ;也可以自定义启动文件 main.lua ,在里面使用内置的 bee.filesystem 模块。

例如,可以参考 vaststars 项目的启动文件处理方法:https://github.com/ejoy/vaststars/blob/master/clibs/bootstrap.lua#L54-L61

默认的启动脚本 main.lua 会将命令行参数中的文件名转发给 engine/console/bootstrap.lua ,这个文件所在的目录会被当做项目的根目录。如果的启动流程会切换当前路径,注意这个文件最好使用绝对路径,或在你自定义的启动流程中做做正确的转换,让引擎可以计算出正确的项目根目录。

以下有一个简易的游戏项目启动脚本可供参考。假设游戏项目完全由 Lua 编写,没有任何 C 代码,那么你不需要编写额外的构建脚本,把游戏项目放在和 ant 同级的目录下。在游戏目录下运行这个脚本,它会先用 realpath 计算出当前目录下 main.lua 的完整路径,并将当前目录切换到 ../lua 并运行 ../ant/bin/mingw/release/ant.exe 传入带有完整路径的 main.lua 。

#!/bin/bash

ANT=../ant
ANTBIN=$ANT/bin/mingw/release/ant.exe
STARTUP=`realpath main.lua`

cd $ANT
$ANTBIN $STARTUP

vfs c模块

这是一个特殊的c模块,它已经被硬编码了在调用luaL_openlibs时加载,所以所有的lua vm在加载第一个lua脚本前,vfs模块就已经被加载。它主要实现了两个特性:

  1. 定义了全局变量__ANT_RUNTIME__,它会在本地模式为nil,运行模式为true。因为每个lua vm都会加载vfs模块,所以你可以在任何lua代码里使用__ANT_RUNTIME__来判断现在运行在哪个模式。 2.实现了一个简易的vfs.read, 并用vfs.read重新实现了loadfile,dofine,package。

ant中处理io.open其余的文件io函数都是使用vfs路径,vfs c模块就是用零依赖的情况下实现这一特性(部分)。具体如下:

  1. 本地模式,会将第一个"/"去掉然后作为localfs路径使用。dofile "/xx/xx.lua" 会加载ant目录下的xx/xx.lua。(因为当前目录已经是ant根目录)
  2. 运行模式,可以加载/engine/firmware/目录下的文件。/engine/firmware/目录下的文件都已经被编译到exe里,所以vfs可以利用firmware模块加载它。

它会用来加载ant的初始化代码,ltask初始化代码以及IO服务等,以用来初始化一个功能能强大的vfs函数(例如包含网络请求,读取zip包,资源惰性编译等等)。

ltask

Ant 引擎工作在 ActorModel 下,这是由 ltask 实现的。引擎在 engine/firmware/bootstrap.lua 中会启动 ltask ,加载必要的服务:io 、timer、logger 。

其中,所有的 VFS 请求都会通过 io 服务,包括引擎自身的代码;timer 和 logger 时 ltask 必要的基础设施。

命令行传入的 lua 文件将以一个服务的形式启动,所以你可以在这个文件中使用 ltask 的 api 。

ltask的启动脚本中会为每个ltask的服务(除了个别特殊的服务例如IO服务)注册vfs系列的函数包括vfs.read,vfs.list,vfs.type等,这些vfs函数实际上都是转发给IO服务处理。其中vfs.read会覆盖vfs c模块中注册的简易版vfs.read。

IO服务

本地模式的IO服务是/engine/console/io.lua,运行模式的IO服务是/engine/firmware/io.lua。它们的主要目的都为ltask其他服务提供vfs.read,vfs.list,vfs.type等函数的支持。但基于两个模式的不同特性和需求,支持的功能又大有不同。本地模式和运行模式大多数差异也是来自于IO服务。

运行模式:会依次在本地目录,zip包,文件服务器查找所需文件。从文件服务器获取的文件会缓存在本地目录。运行模式的IO服务没有编译资源的功能,但可以让文件服务器编译。 本地模式:不依赖文件服务器,会直接读取引擎和项目目录的文件,也提供了资源编译的功能。

vfs lua模块

相对于vfs c模块,lua模块实现了完全的 VFS 特性。VFS 有一套自己的路径映射规则,以实现 Mod 特性,并支持 Asset 的自动编译。它被使用在:

  1. 本地模式的IO服务。
  2. 文件服务器,运行模式的IO服务依赖文件服务器获得 VFS 的特性。
  3. 一些需要直接访问资源或者文件的工具,例如filepack利用它把指定的项目的所有文件和资源编译并打包成为一个zip。

vfs lua模块实现了两个vfs的对象std和tiny。考虑资源编译的情形,资源编译的模块也需要使用vfs对象,但它不能用标准的vfs,因为vfs在正常情况下已经把资源视为了目录,而资源编译又需要读取资源文件本身。所以就有了介于c vfs和std vfs之间的tiny vfs。

VFS启动

在本地模式启动时,会初始化 ant.vfs 这个 Package 中的 mount 子模块 ( 代码位于 pkg/ant.vfs/mount.lua ) 。它会做一些默认的路径映射,也可以在项目根目录下放置一个 DataList 格式的 .mount 文件改写默认规则。

在 .mount 规则中,可以使用 %engine% 指代引擎根路径,它指引擎启动时的当前路径;可以使用 %project% 指代项目根路径,它指命令行传入的启动程序所在的路径。默认的映射规则如下:

mount:
    /engine/ %engine%/engine
    /pkg/    %engine%/pkg
    /        %project%
    /        %project%/mod

源码流程

本地模式:

  1. clibs/vfs/vfs.cpp luaL_openlibs 会加载 vfs c 模块。
  2. runtime/common/runtime.cpp里的内嵌 lua。
  3. !main.lua 加载和 ant.exe 同目录的 main.lua。
  4. /engine/console/bootstrap.lua 真正的初始化起点。
  5. /engine/firmware/ltask.lua 初始化 ltask。
  6. /engine/firmware/ltask_root.lua root 服务。
  7. /engine/console/io.lua io 服务。
  8. /pkg/ant.vfs/** io 服务会加载 vfs lua 模块。
  9. /main.lua 作为一个 ltask 服务被加载。

运行模式:

  1. clibs/vfs/vfs.cpp luaL_openlibs 会加载 vfs c模块。
  2. runtime/common/runtime.cpp 里的内嵌 lua。
  3. /engine/firmware/bootstrap.lua真正的初始化起点。
  4. /engine/firmware/ltask.lua 初始化 ltask。
  5. /engine/firmware/ltask_root.lua root 服务。
  6. /engine/firmware/io.lua io 服务。
  7. /engine/firmware/vfs.lua io 服务会加载 vfs 辅助函数。
  8. /main.lua 作为一个 ltask 服务被加载。
Clone this wiki locally