【重构】重构excel格式,更加优雅清晰

main
walon 2021-10-27 14:15:37 +08:00
parent 881bd3aecc
commit 13309b5da8
21 changed files with 120 additions and 134 deletions

View File

@ -24,7 +24,8 @@ luban相较于常规的excel导表工具有以下核心优势
- 增强了excel格式。可以比较简洁地excel配置**任意复杂**的数据,像子结构、结构列表,以及更复杂的深层次的嵌套结构都能直接解析处理。
- 完备的类型系统和多原始数据支持xml、json、lua、yaml可以轻松表达和解析**任意复杂**的数据。意味着传统excel导表工具无法处理的技能、行为树、副本等等复杂配置luban也能够统一处理了彻底将程序从复杂的配置解析中解放出来。
- 完善的工作流支持。如id的外键引用检查;资源合法性检查;灵活的数据源定义(拆表或者多表合一);灵活的分组导出机制;多种本地化支持;生成极快日常迭代300ms以内Excel2TextDiff工具方便diff查看excel文件的版本间差异
- **=== LubanAssistant Excel插件 ===**。支持把json、lua、xml等文本格式的配置数据加载到excel中批量编辑处理最后再保存回原文件较好地解决大型项目中多人合作数据编辑冲突合并的问题较好解决在编辑器中制作的配置难以在excel中批量修改的问题。
- **LubanAssistant Excel插件**。支持把json、lua、xml等文本格式的配置数据加载到excel中批量编辑处理最后再保存回原文件较好地解决大型项目中多人合作数据编辑冲突合并的问题较好解决在编辑器中制作的配置难以在excel中批量修改的问题。
- 支持自定义代码与数据模板。强大的数据表达能力使得绝大多数项目的配置格式往往是luban的子集因而有较低的项目迁移成本利用模板重新适配代码和数据生成后即使是研发已久或者上线项目也能从luban强大的数据处理能力中受益。
## 文档
@ -45,12 +46,11 @@ luban相较于常规的excel导表工具有以下核心优势
## 特性
- 支持excel族、json、xml、lua、yaml 多种数据格式,基本统一了游戏常见的配置数据
- **强大完备的类型系统**。**可以优雅表达任意复杂的数据结构**。支持所有常见原生类型、datetime类型、容器类型list,set,map、枚举和结构、**多态结构**以及**可空类型**。
- 支持增强的excel格式。可以在excel里比较简洁填写出非常复杂的数据比如顶层字段包含"list,A"类型字段, 而A是结构并且其中又包含"list,B"类型字段B也是结构并且包含"list,C"这样的字段...)。
- 生成代码清晰易读、良好模块化。特地支持运行时原子性热更新配置。
- 生成极快。支持常规的本地缓存增量生成模式也支持云生成模式。MMORPG这样大型项目也能秒内生成。日常增量生成基本在300ms以内项目后期极大节省了迭代时间。另外支持**watch监测模式**,数据目录变化立即重新生成。
- 支持增强的excel格式。可以在excel里比较简洁填写出任意复杂的嵌套数据。
- 生成代码清晰易读、良好模块化。支持指定变量命名风格约定。特地支持运行时原子性热更新配置。
- 灵活的数据源定义。一个表可以来自多个文件或者一个文件内定义多个表或者一个目录下所有文件甚至来自云表格,以及以上的组合
- 支持表与字段级别分组。可以灵活定义分组,选择性地导出客户端或者服务器或编辑器所用的表及字段
- 多种导出数据格式支持。支持binary、json、lua、xml导出数据格式
- 多种导出数据格式支持。支持binary、json、lua、xml、erlang、**xlsx** 及自定义的导出数据格式
- 强大灵活的定制能力
- 支持代码模板,可以用自定义模板定制生成的代码格式
- **支持数据模板**可以用模板文件定制导出格式。意味着可以在不改动现有程序代码的情况下把luban当作**配置处理前端**生成自定义格式的数据与自己项目的配置加载代码配合工作。开发已久的项目或者已经上线的老项目也能从luban强大的数据处理工作流中获益
@ -66,7 +66,10 @@ luban相较于常规的excel导表工具有以下核心优势
- 支持文本静态本地化。导出时所有text类型数据正确替换为最终的本地化字符串。绝大多数的业务功能不再需要运行根据本地化id去查找文本的内容简化程序员的工作。
- 支持文本动态本地化。运行时动态切换所有text类型数据为目标本地化字符串。
- 支持 main + patches 数据合并。在基础数据上,施加差分数据,生成最终完整数据,适用于制作有细微不同的多地区的配置数据。
- [TODO] 【独创】 支持任意粒度和任意类型数据如int,bean,list,map的本地化。
- [TODO] 【独创】 支持任意粒度和任意类型数据如int,bean,list,map的本地化。
- 生成极快。支持常规的本地缓存增量生成模式也支持云生成模式。MMORPG这样大型项目也能秒内生成。日常增量生成基本在300ms以内项目后期极大节省了迭代时间。另外支持**watch监测模式**,数据目录变化立即重新生成。
- **LubanAssistant**Luban的Excel插件。支持把json、lua、xml等文本格式的配置数据加载到excel中批量编辑处理最后再保存回原文件较好地解决大型项目中多人合作数据编辑冲突合并的问题较好解决在编辑器中制作的配置难以在excel中批量修改的问题。
- Excel2TextDiff。将excel转成文本后再diff清晰对比excel版本之间内容变化。
- 支持主流的游戏开发语言
- c++ (11+)
- c# (.net framework 4+. dotnet core 3+)
@ -95,10 +98,6 @@ luban相较于常规的excel导表工具有以下核心优势
- 其他家基于js的小程序平台
- 其他所有支持lua的引擎和平台
- 其他所有支持js的引擎和平台
- 扩展工具
- Excel2TextDiff。将excel转成文本后再diff清晰对比excel版本之间内容变化。
- **LubanAssistant**Luban的Excel插件。支持把json、lua、xml等文本格式的配置数据加载到excel中批量编辑处理最后再保存回原文件较好地解决大型项目中多人合作数据编辑冲突合并的问题较好解决在编辑器中制作的配置难以在excel中批量修改的问题。
## 增强的excel格式
luban支持在excel中解析任意复杂的数据结构哪怕复杂如技能、行为树但在实践中一般使用编辑器制作这些数据以json格式保存而不会在excel里填写。下面从简单到复杂展示在luban中配置这些数据的方式。

View File

@ -1,4 +1,5 @@
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.Defs;
using System.Collections.Concurrent;
using System.Collections.Generic;

View File

@ -1,4 +1,5 @@
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.Defs;
using Luban.Job.Cfg.Utils;
using System;

View File

@ -1,5 +1,6 @@
using Bright.Serialization;
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.DataVisitors;
using Luban.Job.Cfg.Defs;
using System.Collections.Generic;

View File

@ -1,4 +1,5 @@
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.DataVisitors;
using Luban.Job.Cfg.Defs;
using System.Collections.Generic;

View File

@ -1,4 +1,5 @@
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.DataVisitors;
using Luban.Job.Cfg.Defs;
using System;

View File

@ -1,4 +1,5 @@
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.DataVisitors;
using Luban.Job.Cfg.Defs;
using System;

View File

@ -1,4 +1,5 @@
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.DataVisitors;
using Luban.Job.Cfg.Defs;
using System.Collections.Generic;

View File

@ -58,10 +58,10 @@ namespace Luban.Job.Cfg.DataSources.Excel
{
try
{
foreach (TitleRow row in sheet.GetRows())
foreach (var r in sheet.GetRows())
{
var tagRow = row.GetSubTitleNamedRow(TAG_KEY);
string tagStr = tagRow?.Current?.ToString();
TitleRow row = r.Row;
string tagStr = r.Tag;
if (DataUtil.IsIgnoreTag(tagStr))
{
continue;

View File

@ -14,8 +14,6 @@ namespace Luban.Job.Cfg.DataSources.Excel
public string TableName { get; set; }
public int TitleRowCount { get; set; }
public List<List<Cell>> Cells { get; set; }
}
}

View File

@ -14,9 +14,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
public string Type { get; set; }
public string BriefDesc { get; set; }
public string DetailDesc { get; set; }
public string Desc { get; set; }
}
class RawSheetTableDefInfo

View File

@ -20,7 +20,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
public string RawUrl { get; }
public List<TitleRow> Rows { get; } = new();
public List<(string Tag, TitleRow Row)> Rows { get; } = new();
public Sheet(string rawUrl, string name)
{
@ -28,6 +28,11 @@ namespace Luban.Job.Cfg.DataSources.Excel
this.Name = name;
}
private string GetRowTag(List<Cell> row)
{
return row.Count > 0 ? row[0].Value?.ToString() ?? "" : "";
}
public void Load(RawSheet rawSheet)
{
var cells = rawSheet.Cells;
@ -41,14 +46,14 @@ namespace Luban.Job.Cfg.DataSources.Excel
{
continue;
}
Rows.Add(ParseOneLineTitleRow(title, row));
Rows.Add((GetRowTag(row), ParseOneLineTitleRow(title, row)));
}
}
else
{
foreach (var oneRecordRows in SplitRows(title, cells))
{
Rows.Add(ParseMultiLineTitleRow(title, oneRecordRows));
Rows.Add((GetRowTag(oneRecordRows[0]), ParseMultiLineTitleRow(title, oneRecordRows)));
}
}
}
@ -178,7 +183,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
}
}
public IEnumerable<TitleRow> GetRows()
public IEnumerable<(string Tag, TitleRow Row)> GetRows()
{
return Rows;
}

View File

@ -13,10 +13,6 @@ namespace Luban.Job.Cfg.DataSources.Excel
{
private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger();
private const int TITLE_MIN_ROW_NUM = 2;
private const int TITLE_MAX_ROW_NUM = 10;
private const int TITLE_DEFAULT_ROW_NUM = 3;
public static System.Text.Encoding DetectCsvEncoding(Stream fs)
{
Ude.CharsetDetector cdet = new Ude.CharsetDetector();
@ -65,48 +61,18 @@ namespace Luban.Job.Cfg.DataSources.Excel
private static RawSheet ParseRawSheet(IExcelDataReader reader)
{
bool orientRow;
int titleRowNum;
if (!TryParseMeta(reader, out orientRow, out titleRowNum, out var tableName))
if (!TryParseMeta(reader, out orientRow, out var tableName))
{
return null;
}
var cells = ParseRawSheetContent(reader, orientRow);
var title = ParseTitle(cells, reader.MergeCells, orientRow, out _);
cells.RemoveRange(0, Math.Min(titleRowNum, cells.Count));
return new RawSheet() { Title = title, TitleRowCount = titleRowNum, TableName = tableName, Cells = cells };
var cells = ParseRawSheetContent(reader, orientRow, false);
var title = ParseTitle(cells, reader.MergeCells, orientRow);
cells.RemoveAll(c => c.Count == 0 || IsHeaderRow(c));
return new RawSheet() { Title = title, TableName = tableName, Cells = cells };
}
private static int GetTitleRowNum(CellRange[] mergeCells, bool orientRow)
{
if (mergeCells == null)
{
return 1;
}
if (orientRow)
{
foreach (var mergeCell in mergeCells)
{
if (mergeCell.FromRow == 1 && mergeCell.FromColumn == 0)
{
return mergeCell.ToRow - mergeCell.FromRow + 1;
}
}
}
else
{
foreach (var mergeCell in mergeCells)
{
if (mergeCell.FromColumn == 1 && mergeCell.FromRow == 0)
{
return mergeCell.ToColumn - mergeCell.FromColumn + 1;
}
}
}
return 1;
}
public static Title ParseTitle(List<List<Cell>> cells, CellRange[] mergeCells, bool orientRow, out int titleRowNum)
public static Title ParseTitle(List<List<Cell>> cells, CellRange[] mergeCells, bool orientRow)
{
var rootTitle = new Title()
{
@ -117,9 +83,9 @@ namespace Luban.Job.Cfg.DataSources.Excel
ToIndex = cells.Select(r => r.Count).Max() - 1
};
titleRowNum = GetTitleRowNum(mergeCells, orientRow);
//titleRowNum = GetTitleRowNum(mergeCells, orientRow);
ParseSubTitles(rootTitle, cells, mergeCells, orientRow, 1, titleRowNum);
ParseSubTitles(rootTitle, cells, mergeCells, orientRow, 1);
rootTitle.Init();
@ -168,10 +134,10 @@ namespace Luban.Job.Cfg.DataSources.Excel
return (titleName, tags);
}
private static void ParseSubTitles(Title title, List<List<Cell>> cells, CellRange[] mergeCells, bool orientRow, int curDepth, int maxDepth)
private static void ParseSubTitles(Title title, List<List<Cell>> cells, CellRange[] mergeCells, bool orientRow, int curDepth)
{
var titleRow = cells[curDepth - 1];
var rowIndex = curDepth - 1;
var titleRow = cells[rowIndex];
if (mergeCells != null)
{
foreach (var mergeCell in mergeCells)
@ -180,7 +146,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
if (orientRow)
{
//if (mergeCell.FromRow <= 1 && mergeCell.ToRow >= 1)
if (mergeCell.FromRow == curDepth && mergeCell.FromColumn >= title.FromIndex && mergeCell.FromColumn <= title.ToIndex)
if (mergeCell.FromRow == rowIndex && mergeCell.FromColumn >= title.FromIndex && mergeCell.FromColumn <= title.ToIndex)
{
var nameAndAttrs = titleRow[mergeCell.FromColumn].Value?.ToString()?.Trim();
if (IsIgnoreTitle(nameAndAttrs))
@ -194,7 +160,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
}
else
{
if (mergeCell.FromColumn == curDepth - 1 && mergeCell.FromRow - 1 >= title.FromIndex && mergeCell.FromRow - 1 <= title.ToIndex)
if (mergeCell.FromColumn == rowIndex && mergeCell.FromRow - 1 >= title.FromIndex && mergeCell.FromRow - 1 <= title.ToIndex)
{
// 标题 行
var nameAndAttrs = titleRow[mergeCell.FromRow - 1].Value?.ToString()?.Trim();
@ -211,9 +177,9 @@ namespace Luban.Job.Cfg.DataSources.Excel
continue;
}
if (curDepth < maxDepth)
if (curDepth < cells.Count && IsSubFieldRow(cells[curDepth]))
{
ParseSubTitles(subTitle, cells, mergeCells, orientRow, curDepth + 1, maxDepth);
ParseSubTitles(subTitle, cells, mergeCells, orientRow, curDepth + 1);
}
title.AddSubTitle(subTitle);
@ -229,9 +195,9 @@ namespace Luban.Job.Cfg.DataSources.Excel
}
var (titleName, tags) = ParseNameAndMetaAttrs(nameAndAttrs);
if (title.SubTitles.TryGetValue(titleName, out var oldTitle))
if (title.SubTitles.TryGetValue(titleName, out var subTitle))
{
if (oldTitle.FromIndex != i)
if (subTitle.FromIndex != i)
{
throw new Exception($"列:{titleName} 重复");
}
@ -240,54 +206,46 @@ namespace Luban.Job.Cfg.DataSources.Excel
continue;
}
}
title.AddSubTitle(new Title() { Name = titleName, Tags = tags, FromIndex = i, ToIndex = i });
subTitle = new Title() { Name = titleName, Tags = tags, FromIndex = i, ToIndex = i };
if (curDepth < cells.Count && IsSubFieldRow(cells[curDepth]))
{
ParseSubTitles(subTitle, cells, mergeCells, orientRow, curDepth + 1);
}
title.AddSubTitle(subTitle);
}
}
public static bool TryParseMeta(List<string> cells, out bool orientRow, out int titleRows, out string tableName)
public static bool TryParseMeta(string metaStr, out bool orientRow, out string tableName)
{
orientRow = true;
titleRows = TITLE_DEFAULT_ROW_NUM;
tableName = "";
// meta 行 必须以 ##为第一个单元格内容,紧接着 key:value 形式 表达meta属性
if (cells.Count == 0 || cells[0] != "##")
if (!metaStr.StartsWith("##"))
{
return false;
}
foreach (var attr in cells.Skip(1))
foreach (var attr in metaStr.Substring(2).Split("&"))
{
if (string.IsNullOrWhiteSpace(attr))
{
continue;
}
var ss = attr.Split('=');
if (ss.Length != 2)
{
throw new Exception($"单元薄 meta 定义出错. attribute:{attr}");
}
string key = ss[0].Trim();
string value = ss[1].Trim();
var sepIndex = attr.IndexOf('=');
string key = sepIndex >= 0 ? attr.Substring(0, sepIndex) : attr;
string value = sepIndex >= 0 ? attr.Substring(sepIndex + 1) : "";
switch (key)
{
case "orientation":
case "row":
{
orientRow = DefUtil.ParseOrientation(value);
orientRow = true;
break;
}
case "title_rows":
case "column":
{
if (!int.TryParse(value, out var v))
{
throw new Exception($"单元薄 meta 定义 title_rows:{value} 属性值只能为整数[{TITLE_MIN_ROW_NUM},{TITLE_MAX_ROW_NUM}]");
}
if (v < TITLE_MIN_ROW_NUM || v > TITLE_MAX_ROW_NUM)
{
throw new Exception($"单元薄 title_rows 应该在 [{TITLE_MIN_ROW_NUM},{TITLE_MAX_ROW_NUM}] 范围内,默认是{TITLE_DEFAULT_ROW_NUM}");
}
titleRows = v;
orientRow = false;
break;
}
case "table":
@ -297,31 +255,56 @@ namespace Luban.Job.Cfg.DataSources.Excel
}
default:
{
throw new Exception($"非法单元薄 meta 属性定义 {attr}, 合法属性有: orientation=r|row|c|column,title_rows=<number>,table=<tableName>");
throw new Exception($"非法单元薄 meta 属性定义 {attr}, 合法属性有: row,column,table=<tableName>");
}
}
}
return true;
}
public static bool TryParseMeta(IExcelDataReader reader, out bool orientRow, out int titleRows, out string tableName)
public static bool TryParseMeta(IExcelDataReader reader, out bool orientRow, out string tableName)
{
if (!reader.Read() || reader.FieldCount == 0)
{
orientRow = true;
titleRows = TITLE_DEFAULT_ROW_NUM;
tableName = "";
return false;
}
var cells = new List<string>();
for (int i = 0, n = reader.FieldCount; i < n; i++)
{
cells.Add(reader.GetString(i)?.Trim());
}
return TryParseMeta(cells, out orientRow, out titleRows, out tableName);
string metaStr = reader.GetString(0)?.Trim();
return TryParseMeta(metaStr, out orientRow, out tableName);
}
private static List<List<Cell>> ParseRawSheetContent(IExcelDataReader reader, bool orientRow, int? maxParseRow = null)
private static bool IsSubFieldRow(List<Cell> row)
{
if (row.Count == 0)
{
return false;
}
var s = row[0].Value?.ToString()?.Trim();
return s == "##field";
}
private static bool IsTypeRow(List<Cell> row)
{
if (row.Count == 0)
{
return false;
}
var s = row[0].Value?.ToString()?.Trim();
return s == "##type";
}
private static bool IsHeaderRow(List<Cell> row)
{
if (row.Count == 0)
{
return false;
}
var s = row[0].Value?.ToString()?.Trim();
return !string.IsNullOrEmpty(s) && s.StartsWith("##");
}
private static List<List<Cell>> ParseRawSheetContent(IExcelDataReader reader, bool orientRow, bool headerOnly)
{
// TODO 优化性能
// 几个思路
@ -330,7 +313,7 @@ namespace Luban.Job.Cfg.DataSources.Excel
// 3. 跳过null或者empty的单元格
var originRows = new List<List<Cell>>();
int rowIndex = 0;
while (reader.Read())
do
{
++rowIndex; // 第1行是 meta 标题及数据行从第2行开始
var row = new List<Cell>();
@ -339,11 +322,11 @@ namespace Luban.Job.Cfg.DataSources.Excel
row.Add(new Cell(rowIndex, i, reader.GetValue(i)));
}
originRows.Add(row);
if (orientRow && maxParseRow != null && originRows.Count > maxParseRow)
if (orientRow && headerOnly && !IsHeaderRow(row))
{
break;
}
}
} while (reader.Read());
List<List<Cell>> finalRows;
@ -401,22 +384,22 @@ namespace Luban.Job.Cfg.DataSources.Excel
private static RawSheetTableDefInfo ParseSheetTableDefInfo(string rawUrl, IExcelDataReader reader)
{
bool orientRow;
int headerRowNum;
if (!TryParseMeta(reader, out orientRow, out headerRowNum, out var _))
if (!TryParseMeta(reader, out orientRow, out var _))
{
return null;
}
var cells = ParseRawSheetContent(reader, orientRow, headerRowNum);
var title = ParseTitle(cells, reader.MergeCells, orientRow, out int titleRowNum);
var cells = ParseRawSheetContent(reader, orientRow, true);
var title = ParseTitle(cells, reader.MergeCells, orientRow);
if (cells.Count <= titleRowNum)
int typeRowIndex = cells.FindIndex(row => IsTypeRow(row));
if (typeRowIndex < 0)
{
throw new Exception($"缺失type行");
}
List<Cell> typeRow = cells[titleRowNum];
List<Cell> briefDescRow = cells.Count > titleRowNum + 1 ? cells[titleRowNum + 1] : null;
List<Cell> destailDescRow = cells.Count > titleRowNum + 2 ? cells[titleRowNum + 2] : briefDescRow;
List<Cell> typeRow = cells[typeRowIndex];
List<Cell> descRow = cells.Count > typeRowIndex + 1 ? cells[typeRowIndex + 1] : null;
var fields = new Dictionary<string, FieldInfo>();
foreach (var subTitle in title.SubTitleList)
@ -429,9 +412,8 @@ namespace Luban.Job.Cfg.DataSources.Excel
{
Name = subTitle.Name,
Tags = title.Tags,
Type = typeRow?[subTitle.FromIndex].Value?.ToString() ?? "",
BriefDesc = briefDescRow?[subTitle.FromIndex].Value?.ToString() ?? "",
DetailDesc = destailDescRow?[subTitle.FromIndex].Value?.ToString() ?? "",
Type = typeRow[subTitle.FromIndex].Value?.ToString() ?? "",
Desc = descRow?[subTitle.FromIndex].Value?.ToString() ?? "",
});
}

View File

@ -1,6 +1,7 @@
using System.Collections.Generic;
using Luban.Job.Cfg.Datas;
using System.Collections.Generic;
namespace Luban.Job.Cfg.Datas
namespace Luban.Job.Cfg.DataSources
{
public class Record
{

View File

@ -1,5 +1,6 @@
using Luban.Common.Utils;
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.Defs;
using Luban.Job.Cfg.Validators;
using Luban.Job.Common.Types;

View File

@ -311,17 +311,7 @@ namespace Luban.Job.Cfg.Defs
throw new Exception($"table:'{table.Name}' file:{file.OriginFile} title:'{name}' type missing!");
}
// 优先取desc行如果为空,则取title行
cf.Comment = f.BriefDesc;
if (string.IsNullOrWhiteSpace(cf.Comment))
{
cf.Comment = f.DetailDesc;
}
if (string.IsNullOrWhiteSpace(cf.Comment))
{
cf.Comment = "";
}
cf.Comment = f.Desc;
cf.Type = attrs[0];

View File

@ -1,5 +1,6 @@
using Bright.Collections;
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
#if !LUBAN_LITE
using Luban.Job.Cfg.l10n;
#endif

View File

@ -1,5 +1,6 @@
using Luban.Job.Cfg.DataConverts;
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.Defs;
using Luban.Job.Cfg.Utils;
using System;

View File

@ -2,6 +2,7 @@
using Luban.Job.Cfg.DataConverts;
using Luban.Job.Cfg.DataExporters;
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.DataVisitors;
using Luban.Job.Cfg.Defs;
using Luban.Job.Cfg.l10n;

View File

@ -1,6 +1,7 @@
using Bright.Collections;
using Luban.Common.Utils;
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using Luban.Job.Cfg.DataVisitors;
using Luban.Job.Cfg.Defs;
using Luban.Job.Cfg.RawDefs;

View File

@ -1,4 +1,5 @@
using Luban.Job.Cfg.Datas;
using Luban.Job.Cfg.DataSources;
using System;
using System.Collections.Concurrent;