项目结构

Lux 的项目结构遵循一个原则:能从目录和文件名表达的信息,不强迫开发者写进配置文件。 lux.toml 只描述项目边界和少量策略;模块、运行域和 part 关系主要来自源码树。

推荐布局

my_addon/
  addon.json
  lux.toml
  src/
    shared/
      inventory/
        module.lux
        sh_items.lux
      hud.lux
    server/
      inventory/
        sv_store.lux
      init.lux
    client/
      inventory/
        cl_panel.lux
      ui.lux

目录含义:

  • src 是 Lux 源码根目录。
  • sharedclientserver 是运行域目录;最近的运行域目录决定默认运行域。
  • inventory/ 这样的普通目录是模块目录。
  • module.luxcl_module.luxsv_module.luxsh_module.lux 是模块入口 part。
  • cl_sv_sh_ 前缀也能声明 part 的默认运行域。

配置文件

当前 lux.toml 支持这些字段:

package_id = "my_addon"
bundle_id = "my_addon"

[target.gmod]
source_root = "src"
out = "generated/lua"
runtime_base = "lux/my-addon"
autorun = true
source_comments = "boundary"

[target.gmod.realm]
unknown_external = "warn"

[target.gmod.extern]
ThirdPartyAddon = "server"
FancyHud.Open = "client"
SharedLibrary = "shared"

路径相对于 lux.toml 所在目录解析。out 是物理输出根;runtime_base 是生成 include / AddCSLuaFile 使用的 GMod 相对路径;autorun 控制是否额外生成 addon 风格入口。开发阶段建议把 out 放到独立位置,避免覆盖手写文件。

这两个路径刻意分离:out 可以指向生成目录、live addon 的 Lua 根,或 gamemode / 框架集成路径;生成 Lua 内部仍然只写 GMod 运行时能解析的相对路径。不要把 runtime_base 当成操作系统目录或 addon 根目录。

模块发现

Lux 项目构建会递归扫描 source_root 下的所有 .lux 文件。模块路径由文件相对路径 推断:

src/shared/hud.lux                    -> hud
src/shared/inventory/module.lux       -> inventory
src/client/inventory/cl_panel.lux     -> inventory
src/server/admin/users.lux            -> admin/users

运行域目录不会进入模块路径。文件名前缀 cl_sv_sh_ 也不会进入模块路径; 它只影响默认运行域。

这意味着下面三个文件属于同一个模块:

src/shared/inventory/module.lux
src/server/inventory/sv_store.lux
src/client/inventory/cl_panel.lux

它们共享一个模块私有作用域,可以互相看见顶层声明,但仍然受运行域检查约束。

模块入口

多 part 模块必须有且只有一个入口 part:

module.lux
cl_module.lux
sv_module.lux
sh_module.lux

入口 part 的职责:

  • part order { ... },声明完整初始化顺序。
  • 放模块级导出,让公开 API 集中可见。
  • 放少量共享常量或轻量工具函数。
  • 作为开发者打开模块时的“主文件”。

单文件模块不需要入口 part;该文件本身就是模块。

Part 顺序

函数声明会在整个模块内提升,所以函数之间可以跨 part 互相调用。非函数顶层局部值 不会按值提升,它们按 part 初始化顺序执行。

默认顺序是入口 part 在前,其余 part 按稳定路径排序。只要顶层初始化有依赖,就应该 在入口 part 写完整顺序。

完整 part order
part order { "module", "sh_items", "sv_store", "cl_panel" }

part beforepart after 适合很小的局部约束。

局部 part 约束
part after "sh_items"
part before "cl_panel"

不要用一串 before / after 编排大型模块。完整编排应使用 part order

运行域推断

默认运行域来自两类信息:

src/client/ui.lux       -> client
src/server/init.lux     -> server
src/shared/hud.lux      -> shared
src/foo/cl_panel.lux    -> client
src/foo/sv_store.lux    -> server
src/foo/sh_items.lux    -> shared

目录和前缀冲突会报错:

src/client/sv_store.lux

这里最近的目录表示 client,文件前缀表示 server,编译器会拒绝它。

输出目录

GMod 后端把所有产物写到 out 下面:

generated/
  lua/
    autorun/
      my_addon.lua
    lux/
      my-addon/
        loader_shared.lua
        loader_client.lua
        loader_server.lua
        client/
        server/

autorun/my_addon.lua 只是可选 forwarder。真正的模块注册、运行域分发和 AddCSLuaFileruntime_base 下的 Lux loader tree 内完成。设置 autorun = false 时仍然生成 loader tree,只是不生成 autorun 入口;已有 gamemode、框架或手写 Lua 可以自己 include 生成的 loader。

Package 根目录

通过 luxc install 安装的 package root 会从 lux.lock 自动加载。package_roots 主要用于本地 package 开发 checkout:

[target.gmod]
package_roots = "local-packages"

包内部同样使用约定目录:

local-packages/acme/ui/src/module.lux
local-packages/acme/macros/compiletime/module.lux
local-packages/acme/ui/host/module.lux

运行时代码进入生成 Lua;编译时代码只在 luxc 内执行;宿主变换用于把特定领域语法 折叠成更小的运行时代码。