-
Notifications
You must be signed in to change notification settings - Fork 401
Package
Lua 从 5.0 开始提供了一个简单的包系统。Lua 5.1 中,这个包系统变得比较复杂,到了 Lua 5.2 又反思了这些不必要的复杂性,然后减去了一些特性,一直到现在都维持着那个简单的系统。
我赞同保持包系统的简单性,但 Ant 不得不面对一些 Lua 原生包机制无法解决的问题。所以,我们不得不额外开发了一套包系统,并同时尽可能的保留 Lua 原生机制。
Ant 的包(Package)是功能和数据的封装单位。一个基于 Lua 编写的的模块是一个包,一组多媒体资产也是一个包。
包自己是一个环境,在环境内部,数据和代码之间可以相互引用。我们不支持类似文件系统那样用 .. 引用上一级目录,一切内部引用都是从这个环境的根向下展开的。同时,我们也可以使用外部引用,即引用其它包内的数据。可以这样看:当用一个字符串引用某组数据时,分为包名和包内路径两部分,它们用 | 分割,前面是包的名字,后面是包内路径。如果不写 | ,那么就默认为当前包。
包的名字是一个字符串,通常我们会用 . 分割。但它并不表示文件系统上的多级目录。一般我们会用带 . 的字符串来做包在文件系统中的目录名。所有的包都在 VFS 的 /pkg 路径下。例如 foo.bar 这个包在 /pkg/foo.bar 路径下,而不是 /pkg/foo/bar 。如果我们要引用 foo.bar 包下的 something ,就会写为 foo.bar|something 。如果它指一段 Lua 代码编写的子模块,通常源文件指的是 /pkg/foo.bar/something.lua 。
如果一个包是用 Lua 代码实现的模块,它应该使用 import_package(pkgname)
引入它。看起来和 Lua 原生机制中的 require 非常相像,但参数是包的名字组成的字符串。import_package
会去对应的包路径下找到其根目录下的 main.lua
这个文件,和 require 一样的方式运行它:即每个 Lua 虚拟机中只会加载一次,并缓存运行结果,通常是一个 table 。这个运行结果会返回。
在包内,你可以像普通 Lua 代码那样使用原生的 require 。只不过,require 会被限制在当前包的环境内。它的路径搜索基于 VFS 中对应包路径。例如,在 foo.bar
这个包的 main.lua 中,如果你写下
local m = require "modname"
require 会去查找 /pkg/foo.bar/modname.lua
。但你可以在不同的包下使用相同的模块名,require 找到的会是不同的源文件。
require 不支持引用外部包里的子模块,工作环境不能超出当前包。
Ant 基于 ECS 结构设计。ECS 的数据被 world 隔离。同一个 Lua 虚拟机中可以同时存在多个 ECS 的 world 。通常,我们希望这些不同的 world 间引用的数据都是相互独立的。如果需要这种独立性,那么在 ECS 框架下使用 require 就是不合适的。因为同一个 Lua 虚拟机中,require 的结果是被缓存的,多次 require 同一个模块名,会得到同一个 table ,状态是共享的。
我们应该使用 ecs.require()
让它们在不同 world 间隔离开。ECS 专属模块和普通的代码包不同,它没有根文件,任何一个 ecs 模块都必须是某个包内部的一个子模块。
ECS 框架下的子模块的特征是,.lua 文件的第一行一定是
local ecs = ...
这表示是一个 ECS 子模块,会由 ecs.require()
传入 context 给 ecs 变量,赋予它独立环境。这个模块内也可以使用 ECS 框架的专有 API 。
当我们写 ecs.require "module"
其实查找的是当前包内的 module.lua 这个源文件,它的第一行一定是 local ecs = ...
。和原生 require 不同,ecs.require
还可以引用外部包中的 ECS 模块,我们可以写 ecs.require "foo.bar|module"
,它会去查找 /pkg/foo.bar/module.lua
这个文件。
特性是 Ant 引擎的 ECS 架构中的一个概念。当使用 import_feature(name)
时,将引入定义在 name 这个包内的主特性。import_feature()
会解析 /pkg/name/package.ecs
这个特性定义文件。
Ant 引擎使用 ltask 来使用处理器的多核,并将功能模块分离到不同的服务中。这些服务各自处于独立的 Lua 虚拟机,被 ltask 调度到不同的线程运行。服务间通过消息通讯,而不能直接共享状态。简单来说,一个服务中用 Lua 定义的全局变量,另一个服务的 Lua 代码是无法访问的。
服务也是用 Lua 实现,它的源代码也是放在包系统内。引擎规定,所有服务的主文件必须放在某个包的 service 目录下。例如,引擎内置了一个 webserver 用于游戏在运行时窥探自身的内部状态,开发者可以通过浏览器连上这个 webserver 查看运行时的各种数据。
这个 webserver 被实现为一个服务,这个服务放在 ant.webserver 这个包里面。那么,该服务的源代码就处于 vfs 中的 /pkg/ant.webserver/service/webserver.lua
启动这个服务可以用:
ltask.uniqueservice "ant.webserver|webserver"
ant.webserver|webserver
描述了这是一个位于 ant.webserver
包中名为 webserver 的服务。
一个包中可以同时包含有代码包、ECS 模块或特性、ltask 服务的实现。也可以不包含以上任何东西,纯粹是一组数据。通常,多媒体资产就放在这种纯数据包里面。对于 Ant 引擎来说,几乎全部代码和数据都是由一个个的包构成的。除了少许例外:
- 第一个启动脚本。
- /engine 放置了一些自举用的代码。
- 通常会把内置 webserver 用到的静态文件放在 /web 下。
- VFS 会把资产编译结果放在 /res 下,然后通过软连接被某些包引用。
- 其它开发者不愿意用包管理的数据。是否使用包机制是一个可选项,我们推荐它,但不强制使用。