organize structure

main
bug 2020-11-08 21:22:12 +08:00
parent 56a6293715
commit ef4addef1f
4 changed files with 246 additions and 153 deletions

147
README.md
View File

@ -9,151 +9,22 @@
## 介绍 ## 介绍
Luban 是一个强大的生成与缓存工具。生成目标可以是常规代码、配置数据、类似protobuf的消息代码也可以是游戏资源如assetbundle。 - Luban 是一个强大的配置生成与缓存工具。
在大型项目中由于配置或资源数据庞大生成对象可能会花费相当多的时间。比如一个典型的MMORPG项目后期全量生成配置即使用了多线程加速所需时间 - Luban 最初是为了解决传统excel导出工具功能过于薄弱无法很好处理 MMORPG 游戏复杂配置需求的痛点问题。
也在10秒的级别。因此使用client/server模式通过缓存机制来加速生成过程。 - 生成目标可以是常规代码、配置数据、类似 protobuf 的消息代码,也可以是游戏资源如 assetbundle。
- 在大型项目中,由于配置或资源数据庞大,生成对象可能会花费相当多的时间。
Luban 最初是为了解决传统excel导出工具功能过于薄弱无法很好处理MMORPG游戏复杂配置需求的痛点问题。 自2015年以来经历过 MMORPG、卡牌、SLG 等多个上线项目的考验, - 比如一个典型的MMORPG项目后期全量生成配置即使用了多线程加速所需时间也在10秒的级别。
实际项目过程中不断迭代和优化,最终由一个增强型的配置工具成为一个 **相对完备的游戏配置数据解决方案**。 - 因此除了使用缓存,还使用了 client/server 模式,来加速生成过程。
- 自2015年以来经历过 MMORPG、卡牌、SLG 等多个上线项目的考验,
- 实际项目过程中不断迭代和优化,最终由一个增强型的配置工具成为一个相对完备的游戏配置数据解决方案。
## 文档 ## 文档
* [主页](https://focus-creative-games.github.io/luban/index.html) * [主页](https://focus-creative-games.github.io/luban/index.html)
* 各语言的简介: [English](README.en-us.md), [简体中文](README.md) * 各语言的简介: [English](README.en-us.md), [简体中文](README.md)
* [特性](docs/traits.md)
* [使用说明](docs/catalog.md) * [使用说明](docs/catalog.md)
## 特性
### 完备的类型系统
* 基础内置类型
- bool,byte,short,fshort,int,fint,long,flong,float,double,string,text,bytes
- vector2, vector3,vector4
- datetime
* 可空类型
- bool?,byte?,short?,fshort?,int?,fint?,long?,flong?,float?,double?
- vector2?,vector3?,vector4?
- datetime?
* 自定义枚举 enum 及相应可空类型
* 自定义常量 const
* 自定义结构 bean
* 多态bean
支持定义无限层次的OOP类型继承体系(比如父类Shape子类Circle,Rectangle),在表达复杂配置时极为简洁,对程序和策划都比较友好。
* 支持容器类型 array。 value 可以为内置类型,也可以为自定义类型
* 支持容器类型 list。 value 可以为内置类型,也可以为自定义类型
* 支持容器类型 set。 value 只能为内置类型或者enum类型不支持 bean 类型
* 支持容器类型 map。 key 只能为内置类型或者enum类型不支持 bean 类型。 value 可以为内置类型,也可以为自定义类型
### 支持增强的excel格式
* 用 true,false表示 bool变量。
* 用枚举名及别名表示枚举常量。比如用 白绿红之类表示品质而不是1,2,3这样的magic number
* 支持整数的常量替换。比如说 升级丹道具id 为 1122所有填升级丹id的地方可以填 升级丹 来表示。减少填写错误
* 支持可空变量. 用 null 表示空数据.
* 支持 datetime 数据类型. 时间格式标准为以下几种最终被转化为utc时间方便程序处理
- yyyy-MM-dd HH:mm:ss
- yyyy-MM-dd HH:mm
- yyyy-MM-dd HH
- yyyy-MM-dd
* 支持用sep拆分单元格。在一个单元格里填写多个数据。
* 支持多行数据。例如,章节配置里有一个复杂小节列表字段。支持多行填写这个列表数据。
* 支持多级标题头,方便对应一些比较深的数据。比如 a.b.c 这种。
* 支持多态别名,可以方便填写多态数据。比如说 Circle,5 或者 Rectangle,3,4
* **支持在excel里比较简洁填写出任意复杂的配置**。
- 支持结构列表。 比如 list,Equip (包含 int id, string name, float attr) 这样一个复杂结构列表数据,可以填成 1,abasfa,0.5|2,xxxyy;0.9。
- 支持多态结构。 比如 cfg.Shape 是一个多态类型,包含 Cirecle(float radius)和Rectagnle(float width, float size)。 可以填成 圆,5 或者 长方形,3,5。
- 支持无限层次的复杂结构的组合
- 比如 list,Convex(int id, Vector3[] vertexs) 是一个多边形列表, Convext自身包含一个顶点列表可以配置成 1_1.2,2.3,3.4_3.1,3.2,3.3|2_2.2,2.3.3.3 。
- 比如 list,Shape 是一个形状列表。 可以这样配置 Circle,10;Rectange,5,6;Circle,4
### 多种原始文件格式支持
一个复杂项目中,总有一部分数据(10-20%)不适合excel编辑比如技能、AI、副本等等这些数据一般通过专用编辑器来编辑和导出。遇到的问题是这种配置数据是无法与excel数据统一处理的造成游戏内有多种配置数据加载方案程序需要花费很多时间去处理这些数据的加载问题。另外这些复杂数据无法使用数据检验和分组导出以及本地化等等excel导表工具的机制。Luban能够处理excel族、xml、json、lua、目录等多种数据源统一导出数据和生成代码所有数据源都能使用数据检验、分组导出等等机制程序彻底从复杂配置处理中解脱出来。
* 支持excel族文件。 csv 及 xls,xlsx等格式
* 支持从指定excel里的某个单元薄读入。
* 支持json。 每个json文件当作一个记录读入
* 支持lua。 每个lua文件当作一个记录读入
* 支持xml。 每个xml文件当作一个记录读入
* 支持目录。 递归目录下的所有文件每个文件当作一个记录读入。允许不同类型的文件混合比如目录下可以同时有json,lua,xml,excel之类的格式。
* 每个表允许指定多个数据源,可以使用以上所有的组合。
- 多对一。比如可以在一个excel里用不同页签配置不同的表。比如 装备升级表和进阶表都在 装备表.xlsx中
- 一对多。比如任务表可以来 任务1.xlsx任务2.xlsx 等等多个表。
- 多对多。还可以是以上组合,不过实际中很少见)
### 多种导出数据格式支持
**导出格式与原始数据解耦**。无论源数据是 excel、lua、xml、json 或者它们的混合, 最终都被以**统一的格式**导出,极大简化了生成代码的复杂性。 目前支持以下几种导出格式:
* binary格式。与pb格式类似。所占空间最小加载最快。
* json 格式。
* lua 格式。
* 扩展其他格式也很容易。像前几种数据格式导出只用200行代码
### 支持表与字段级别分组
支持自定义分组类型。既支持按分组选择性导出一部分表,也支持选择性导出表中的一部分字段。比如为前后端分别导出他们所用的数据。
### 支持数据标签
支持 是、否、test 三种标签。可以为每行数据加标签。比如标签为"否"表示这行数据不被导出。 如果为 "test",则只在测试导出情况下才导出。比如内部开发时会配置一些测试数据,但对外发布时不希望导出它们的情形。
### 强大的数据校验能力
* 完整的数据内建约束检查
* ref 检查。检查表引用合法性。比如 Monster表中的dropId必须是合法的 TbDrop表的key.
* path 检查。检查资源引用合法性。比如 Monster表的icon必须是合法的图标资源。对于防止策划填错极其有用不再需要运行时才发现资源配置错误了。
* range 检查。检查数值范围。
* 扩展的自定义检查。使用自定义代码进行高级检查。提交配置前就能完成本地检查,避免运行时才发现错误,极大减少迭代成本。
### 多种数据表模式
* one 格式,即单例表模式
* map 格式即普通key-value表模式。 任何符合set 的value要求的类型都可以做key
* bmap 格式,即双主键模式。 任何符合 set 的value要求的类型都可以作 key1和key
### 本地化支持
* 支持**本地化时间**。 配置中的 datetime会根据指定的timezone及localtime转化为正确的utc时间方便程序处理
* 支持**静态本地化**。 配置中的text类型在导出时已经转化为正确的本地化后的字符串
* 支持**动态本地化**。 配置中的text类型能运行时全部切换为某种本地化后的字符串
### 代码编辑器支持
支持 emmylua anntations。生成的lua包含符合emmylua 格式anntations信息。配合emmylua有良好的配置代码提示能力。
### 资源导出支持
支持 res 资源标记。可以一键导出配置中引用的所有资源列表(icon,ui,assetbundle等等)
### 代码模块化
生成模块化的代码。比如
- c# cfg.item.ItemInfo
- c++ cfg::item::ItemInfo
- lua item.ItemInfo
- go item_ItemInfo
### 生成极快,大型项目也能秒级导出
使用 client/server模式利用服务器强大的硬件(大内存+多线程)同时配合缓存机制如果数据和定义未修改直接返回之前生成过的结果即使到项目中后期数据规模比较大也能1秒传统在10秒以上左右生成所有数据并且完成数据校验。考虑策划和程序经常使用生成工具积少成多也能节省大量研发时间。
### 数据模块化
策划可以方便地按需求自己组织数据目录和结构,不影响逻辑表。
### 支持主流的游戏开发语言
- c++ (11+)
- c# (.net framework 2+. dotnet core 2+)
- java (1.6+)
- go (1.10+)
- lua (5.1+)
- js 和 typescript (3.0+)
- python (2.7+ 及 3.0+)
### 支持主流引擎和平台
- unity + c#
- unity + tolua,xlua
- unity + ILRuntime
- unreal + c++
- unreal + unlua
- unreal + sluaunreal
- unreal + puerts
- cocos2d-x + lua
- cocos2d-x + js
- 微信小程序平台
- 其他家基于js的小程序平台
- 其他所有支持lua的引擎和平台
- 其他所有支持js的引擎和平台
## 使用示例 ## 使用示例
* Lua 使用示例 * Lua 使用示例

View File

@ -5,14 +5,29 @@
- 新增 一个字段 batch_useable表示能否批量使用 - 新增 一个字段 batch_useable表示能否批量使用
- 用 true 或 false 表示 bool 值,只需要小写后是这两个值即可,比如 true,True,True 都是合法的值。excel 会自动将输入的值大写化。 - 用 true 或 false 表示 bool 值,只需要小写后是这两个值即可,比如 true,True,True 都是合法的值。excel 会自动将输入的值大写化。
- ![如图](images/adv/def_01.png) - 配置:
- ![相应定义文件](images/adv/def_02.png) ![如图](images/adv/def_01.png)
- [定义](images/adv/def_02.png):
``` xml
<module name = "item">
<bean name = "Item">
<var name = "batch_usable" type = "bool" />
</bean>
</module>
```
## float 类型 ## float 类型
- 新增一个 float 类型字段,掉落概率 drop_prob. - 新增一个 float 类型字段,掉落概率 drop_prob.
- ![如图](images/adv/def_03.png) - ![如图](images/adv/def_03.png)
- ![如图](images/adv/def_04.png) - [定义](images/adv/def_04.png):
``` xml
<module name = "item">
<bean name = "Item">
<var name = "drop_prob" type = "float" />
</bean>
</module>
```
## 列表类型 list,int ## 列表类型 list,int

135
docs/traits.md 100644
View File

@ -0,0 +1,135 @@
[//]: # (Author: bug)
[//]: # (Date: 2020-11-08 18:03:58)
## 特性
### 完备的类型系统
* 基础内置类型
- bool,byte,short,fshort,int,fint,long,flong,float,double,string,text,bytes
- vector2, vector3,vector4
- datetime
* 可空类型
- bool?,byte?,short?,fshort?,int?,fint?,long?,flong?,float?,double?
- vector2?,vector3?,vector4?
- datetime?
* 自定义枚举 enum 及相应可空类型
* 自定义常量 const
* 自定义结构 bean
* 多态bean
支持定义无限层次的OOP类型继承体系(比如父类Shape子类Circle,Rectangle),在表达复杂配置时极为简洁,对程序和策划都比较友好。
* 支持容器类型 array。 value 可以为内置类型,也可以为自定义类型
* 支持容器类型 list。 value 可以为内置类型,也可以为自定义类型
* 支持容器类型 set。 value 只能为内置类型或者enum类型不支持 bean 类型
* 支持容器类型 map。 key 只能为内置类型或者enum类型不支持 bean 类型。 value 可以为内置类型,也可以为自定义类型
### 支持增强的excel格式
* 用 true,false表示 bool变量。
* 用枚举名及别名表示枚举常量。比如用 白绿红之类表示品质而不是1,2,3这样的magic number
* 支持整数的常量替换。比如说 升级丹道具id 为 1122所有填升级丹id的地方可以填 升级丹 来表示。减少填写错误
* 支持可空变量. 用 null 表示空数据.
* 支持 datetime 数据类型. 时间格式标准为以下几种最终被转化为utc时间方便程序处理
- yyyy-MM-dd HH:mm:ss
- yyyy-MM-dd HH:mm
- yyyy-MM-dd HH
- yyyy-MM-dd
* 支持用sep拆分单元格。在一个单元格里填写多个数据。
* 支持多行数据。例如,章节配置里有一个复杂小节列表字段。支持多行填写这个列表数据。
* 支持多级标题头,方便对应一些比较深的数据。比如 a.b.c 这种。
* 支持多态别名,可以方便填写多态数据。比如说 Circle,5 或者 Rectangle,3,4
* **支持在excel里比较简洁填写出任意复杂的配置**。
- 支持结构列表。 比如 list,Equip (包含 int id, string name, float attr) 这样一个复杂结构列表数据,可以填成 1,abasfa,0.5|2,xxxyy;0.9。
- 支持多态结构。 比如 cfg.Shape 是一个多态类型,包含 Cirecle(float radius)和Rectagnle(float width, float size)。 可以填成 圆,5 或者 长方形,3,5。
- 支持无限层次的复杂结构的组合
- 比如 list,Convex(int id, Vector3[] vertexs) 是一个多边形列表, Convext自身包含一个顶点列表可以配置成 1_1.2,2.3,3.4_3.1,3.2,3.3|2_2.2,2.3.3.3 。
- 比如 list,Shape 是一个形状列表。 可以这样配置 Circle,10;Rectange,5,6;Circle,4
### 多种原始文件格式支持
一个复杂项目中,总有一部分数据(10-20%)不适合excel编辑比如技能、AI、副本等等这些数据一般通过专用编辑器来编辑和导出。遇到的问题是这种配置数据是无法与excel数据统一处理的造成游戏内有多种配置数据加载方案程序需要花费很多时间去处理这些数据的加载问题。另外这些复杂数据无法使用数据检验和分组导出以及本地化等等excel导表工具的机制。Luban能够处理excel族、xml、json、lua、目录等多种数据源统一导出数据和生成代码所有数据源都能使用数据检验、分组导出等等机制程序彻底从复杂配置处理中解脱出来。
* 支持excel族文件。 csv 及 xls,xlsx等格式
* 支持从指定excel里的某个单元薄读入。
* 支持json。 每个json文件当作一个记录读入
* 支持lua。 每个lua文件当作一个记录读入
* 支持xml。 每个xml文件当作一个记录读入
* 支持目录。 递归目录下的所有文件每个文件当作一个记录读入。允许不同类型的文件混合比如目录下可以同时有json,lua,xml,excel之类的格式。
* 每个表允许指定多个数据源,可以使用以上所有的组合。
- 多对一。比如可以在一个excel里用不同页签配置不同的表。比如 装备升级表和进阶表都在 装备表.xlsx中
- 一对多。比如任务表可以来 任务1.xlsx任务2.xlsx 等等多个表。
- 多对多。还可以是以上组合,不过实际中很少见)
### 多种导出数据格式支持
**导出格式与原始数据解耦**。无论源数据是 excel、lua、xml、json 或者它们的混合, 最终都被以**统一的格式**导出,极大简化了生成代码的复杂性。 目前支持以下几种导出格式:
* binary格式。与pb格式类似。所占空间最小加载最快。
* json 格式。
* lua 格式。
* 扩展其他格式也很容易。像前几种数据格式导出只用200行代码
### 支持表与字段级别分组
支持自定义分组类型。既支持按分组选择性导出一部分表,也支持选择性导出表中的一部分字段。比如为前后端分别导出他们所用的数据。
### 支持数据标签
支持 是、否、test 三种标签。可以为每行数据加标签。比如标签为"否"表示这行数据不被导出。 如果为 "test",则只在测试导出情况下才导出。比如内部开发时会配置一些测试数据,但对外发布时不希望导出它们的情形。
### 强大的数据校验能力
* 完整的数据内建约束检查
* ref 检查。检查表引用合法性。比如 Monster表中的dropId必须是合法的 TbDrop表的key.
* path 检查。检查资源引用合法性。比如 Monster表的icon必须是合法的图标资源。对于防止策划填错极其有用不再需要运行时才发现资源配置错误了。
* range 检查。检查数值范围。
* 扩展的自定义检查。使用自定义代码进行高级检查。提交配置前就能完成本地检查,避免运行时才发现错误,极大减少迭代成本。
### 多种数据表模式
* one 格式,即单例表模式
* map 格式即普通key-value表模式。 任何符合set 的value要求的类型都可以做key
* bmap 格式,即双主键模式。 任何符合 set 的value要求的类型都可以作 key1和key
### 本地化支持
* 支持**本地化时间**。 配置中的 datetime会根据指定的timezone及localtime转化为正确的utc时间方便程序处理
* 支持**静态本地化**。 配置中的text类型在导出时已经转化为正确的本地化后的字符串
* 支持**动态本地化**。 配置中的text类型能运行时全部切换为某种本地化后的字符串
### 代码编辑器支持
支持 emmylua anntations。生成的lua包含符合emmylua 格式anntations信息。配合emmylua有良好的配置代码提示能力。
### 资源导出支持
支持 res 资源标记。可以一键导出配置中引用的所有资源列表(icon,ui,assetbundle等等)
### 代码模块化
生成模块化的代码。比如
- c# cfg.item.ItemInfo
- c++ cfg::item::ItemInfo
- lua item.ItemInfo
- go item_ItemInfo
### 生成极快,大型项目也能秒级导出
使用 client/server模式利用服务器强大的硬件(大内存+多线程)同时配合缓存机制如果数据和定义未修改直接返回之前生成过的结果即使到项目中后期数据规模比较大也能1秒传统在10秒以上左右生成所有数据并且完成数据校验。考虑策划和程序经常使用生成工具积少成多也能节省大量研发时间。
### 数据模块化
策划可以方便地按需求自己组织数据目录和结构,不影响逻辑表。
### 支持主流的游戏开发语言
- c++ (11+)
- c# (.net framework 2+. dotnet core 2+)
- java (1.6+)
- go (1.10+)
- lua (5.1+)
- js 和 typescript (3.0+)
- python (2.7+ 及 3.0+)
### 支持主流引擎和平台
- unity + c#
- unity + tolua,xlua
- unity + ILRuntime
- unreal + c++
- unreal + unlua
- unreal + sluaunreal
- unreal + puerts
- cocos2d-x + lua
- cocos2d-x + js
- 微信小程序平台
- 其他家基于js的小程序平台
- 其他所有支持lua的引擎和平台
- 其他所有支持js的引擎和平台

View File

@ -1,32 +1,102 @@
using Bright.Net;using Bright.Net.Bootstraps; using Bright.Net;
using Bright.Net.Bootstraps;
using Bright.Net.Channels; using Bright.Net.Channels;
using Bright.Net.Codecs;using Bright.Net.ServiceModes.Managers; using Bright.Net.Codecs;
using Bright.Net.ServiceModes.Managers;
using Luban.Common; using Luban.Common;
using Luban.Common.Protos; using Luban.Common.Protos;
using Luban.Server.Common; using Luban.Server.Common;
using System;using System.Collections.Generic;using System.Net; using System;
using System.Collections.Generic;
using System.Net;
namespace Luban.Server{ namespace Luban.Server
public class Session : SessionBase { public override void OnActive() { } public override void OnInactive() { } } public class GenServer : ServerManager<Session> { private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger(); public static GenServer Ins { get; } = new GenServer(); private readonly Dictionary<int, Action<Session, Protocol>> _handlers = new Dictionary<int, Action<Session, Protocol>>(); private readonly Dictionary<string, IJobController> _jobs = new Dictionary<string, IJobController>(); public void Start(int port, Dictionary<int, ProtocolCreator> factories) { {
public class Session : SessionBase
{
public override void OnActive()
{
}
public override void OnInactive()
{
}
}
public class GenServer : ServerManager<Session>
{
private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
public static GenServer Ins { get; } = new GenServer();
private readonly Dictionary<int, Action<Session, Protocol>> _handlers = new Dictionary<int, Action<Session, Protocol>>();
private readonly Dictionary<string, IJobController> _jobs = new Dictionary<string, IJobController>();
public void Start(int port, Dictionary<int, ProtocolCreator> factories)
{
_handlers.Add(GetOutputFile.ID, (s, p) => OnGetOutputFile(s, (GetOutputFile)p)); _handlers.Add(GetOutputFile.ID, (s, p) => OnGetOutputFile(s, (GetOutputFile)p));
_handlers.Add(GenJob.ID, (s, p) => OnGenJob(s, (GenJob)p)); _handlers.Add(GenJob.ID, (s, p) => OnGenJob(s, (GenJob)p));
var worker = new EventLoopGroup(4, 16); var server = new TcpServerBootstrap { LocalAddress = new IPEndPoint(IPAddress.Any, port), ChildrenEventLoopGroup = worker, EventLoop = worker.ChooseEventLoop(), InitChildrenHandler = (s, c) => { var worker = new EventLoopGroup(4, 16);
var server = new TcpServerBootstrap
{
LocalAddress = new IPEndPoint(IPAddress.Any, port),
ChildrenEventLoopGroup = worker,
EventLoop = worker.ChooseEventLoop(),
InitChildrenHandler = (s, c) =>
{
c.Pipeline.AddLast(new ProtocolFrameCodec(20_000_000, new RecycleByteBufPool(100, 100), new DefaultProtocolAllocator(factories))); c.Pipeline.AddLast(new ProtocolFrameCodec(20_000_000, new RecycleByteBufPool(100, 100), new DefaultProtocolAllocator(factories)));
c.Pipeline.AddLast(this); c.Pipeline.AddLast(this);
} }; _ = server.ListenAsync(); } public void RegisterJob(string jobType, IJobController jobController) }
};
_ = server.ListenAsync();
}
public void RegisterJob(string jobType, IJobController jobController)
{ {
s_logger.Info("register job. name:{name} class:{class}", jobType, jobController.GetType().FullName); s_logger.Info("register job. name:{name} class:{class}", jobType, jobController.GetType().FullName);
_jobs.Add(jobType, jobController); _jobs.Add(jobType, jobController);
} protected override void OnAddSession(Session s) { } protected override void OnRemoveSession(Session s) { } protected override void HandleProtocol(Session session, Protocol proto) { s_logger.Trace("session:{id} protocol:{@proto}", session.Id, proto); if (_handlers.TryGetValue(proto.GetTypeId(), out var handler)) }
protected override void OnAddSession(Session s)
{
}
protected override void OnRemoveSession(Session s)
{
}
protected override void HandleProtocol(Session session, Protocol proto)
{
s_logger.Trace("session:{id} protocol:{@proto}", session.Id, proto);
if (_handlers.TryGetValue(proto.GetTypeId(), out var handler))
{ {
handler(session, proto); handler(session, proto);
} else }
else
{ {
s_logger.Error("unknown proto:{proto}", proto); s_logger.Error("unknown proto:{proto}", proto);
} } }
}
private void OnGetOutputFile(Session session, GetOutputFile rpc) { var cache = CacheManager.Ins.FindCache(rpc.Arg.MD5); session.ReplyRpc<GetOutputFile, GetOutputFileArg, GetOutputFileRes>(rpc, new GetOutputFileRes() { Exists = cache != null, FileContent = cache?.Content, }); } private void OnGenJob(Session session, GenJob rpc) private void OnGetOutputFile(Session session, GetOutputFile rpc)
{
var cache = CacheManager.Ins.FindCache(rpc.Arg.MD5);
session.ReplyRpc<GetOutputFile, GetOutputFileArg, GetOutputFileRes>(rpc, new GetOutputFileRes()
{
Exists = cache != null,
FileContent = cache?.Content,
});
}
private void OnGenJob(Session session, GenJob rpc)
{ {
s_logger.Info("onGenJob. arg:{@arg}", rpc.Arg); s_logger.Info("onGenJob. arg:{@arg}", rpc.Arg);
@ -43,4 +113,6 @@ namespace Luban.Server{
FileGroups = new List<FileGroup>(), FileGroups = new List<FileGroup>(),
}); });
} }
} }} }
}
}