skynet 的游戏工程目录结构

服务和接口

参考 skynet 自带的服务,接口放在 lualib/ 目录,服务实现放在 service/ 目录。在此基础上再加一点限制,顶层的 lualib/ 和 service/ 目录只用来实现通用服务,也就是任意进程都可能需要加载的服务,类似于 skynet 自带的那些服务。

比如实现了一个自定义的 logger 服务,这个服务是每个进程都需要加载的。因此目录结构为:

lualib/log
├── bucket
│   ├── console.lua
│   ├── file.lua
│   ├── init.lua
│   └── service.lua
├── formatter.lua
├── init.lua
├── logger.lua
├── log_level.lua
└── util.lua
service/logger
├── bucket.lua
├── checker.lua
├── cmd.lua
├── global.lua
├── main.lua
└── start.lua

还有一些约定,库的入口文件为 init.lua ,这样使用者只需要 local log = require "log" 即可访问到 log/init.lua 文件,这是 Lua 默认的规范,目录下的其他文件就不做规定,按模块的复杂度区分就行。如果模块的接口足够简单,只需要一个文件即可的情况下,那就不需要目录。

服务的入口文件我定为 main.lua 文件,因为服务就像一个独立的程序,因此参考 C 语言的入口函数为 main 一样。服务目录下的文件只允许本服务访问,在 package.path 上做了点手脚实现的:
这段代码是 skynet 官方默认的 loader.lua 里的。

if service_path then
 service_path = string.gsub(service_path, "?", SERVICE_NAME)
 package.path = service_path .. "?.lua;" .. package.path
 SERVICE_PATH = service_path
else
 local p = string.match(pattern, "(.*/).+$")
 SERVICE_PATH = p
end

那如果进程独有的库放哪里呢?我设定的是放在 start 服务的 lualib 目录里,在配置中这样可以实现:

-- 进程独有的库
lua_path = lua_path .. root .. "service/" .. start .. "/lualib/?.lua;"
lua_path = lua_path .. root .. "service/" .. start .. "/lualib/?/init.lua;"

那服务独有的库放哪里?我设定的是放服务的 main.lua 同级目录下的 lualib 目录里。

-- 服务独有的库
package.path = package.path .. SERVICE_PATH .. SERVICE_NAME.. "/lualib/?.lua;"
package.path = package.path .. SERVICE_PATH .. SERVICE_NAME .. "/lualib/?/init.lua;"

上面说了通用服务是放 service 顶层目录里的,那进程独有的服务放哪里呢?那就放进程启动文件的同级目录下:
比如下面的例子:

service/
├── cluster_discovery.lua       # 顶级服务:cluster 的服务发现功能
├── game                        # game 进程
│   ├── login                   # 登录服务
│   │   ├── client.lua
│   │   ├── global.lua
│   │   ├── login_request.lua
│   │   └── main.lua            # 登录服务的入口
│   ├── lualib                  # 进程独有的库
│   │   └── roleagent_api.lua
│   ├── main.lua                # game 启动入口
│   ├── roleagent               # 玩家服务
│   │   ├── client.lua
│   │   ├── global.lua
│   │   ├── main.lua            # 玩家服务入口
│   │   ├── modules             # 逻辑模块
│   │   │   └── role
│   │   │       ├── init.lua
│   │   │       └── request.lua
│   │   └── rolemgr.lua
│   ├── roleagentmgr.lua        # 玩家管理服务
│   └── watchdog.lua            # 玩家网络入口
├── logger                      # 日志服务
│   ├── bucket.lua
│   ├── checker.lua
│   ├── cmd.lua
│   ├── global.lua
│   ├── main.lua                # 日志服务入口
│   └── start.lua
├── mongo_conn.lua              # 数据库代理服务
├── mongo_index.lua             # 数据库索引服务
├── robot                       # 机器人进程
│   ├── main.lua
│   └── robotagent              # 单个机器人
│       └── main.lua
└── sproto_loader.lua           # 协议加载服务

服务里的文件名

从上面的例子中可以看出,服务里有些名字是相同的,这里有些规定的。

global.lua

用于存放全局变量的定义,就是服务内随都可以访问和修改,比如下面这个:

local global = {}

global.watchdog_service = nil
global.roleagentmgr_service = nil

return global

cmd.lua

这个文件是放服务的 rpc 接口的处理入口,用于其他服务 skynet.send 和 skynet.call 的。关于这个接口的封装在另一个文章里写到过:https://blog.hanxi.cc/p/97/

玩家服务

玩家服务将是一个逻辑庞大的服务,会有一堆的客户端协议处理,比如玩家的背包,装备,好友,邮件等等模块的逻辑实现都会在玩家服务上。首先客户端协议文件的定义放在
proto 目录,base.sproto 只放协议头,login.sproto 就是 登录服务用的协议,roleagent 目录下的协议就是玩家服务用的,当一个服务的协议过多时,就需要放一个目录统一管理,这样的好处是,协议注册可以比较方便的实现自动注册。比如扫描服务同名的目录。

proto/
├── base.sproto
├── login.sproto
└── roleagent
    ├── bag.sproto
    ├── item.sproto
    ├── mail.sproto
    └── role.sproto

玩家服务里放了一个 modules 目录,对应协议的 proto/roleagent 目录。modules/*/init.lua
作为模块的入口文件,modules/*/request.lua
作为模块的协议入口文件,模块的目录名字跟协议的文件名字一一对应。这样做的好处就是内聚性更好,request.lua 文件不能 require
其他的 request.lua ,只能 require modules/*/init.lua 这些文件,有点像网页开发里的 route
路由模块。模块目录下的其他文件就不做要求,模块复杂的就可以拆分更多的文件,不复杂就一个 init.lua 即可。

点击进入评论 ...