using Bright.Time; using CommandLine; using Luban.Common.Protos; using Luban.Common.Utils; using Luban.Job.Cfg.Cache; using Luban.Job.Cfg.DataCreators; using Luban.Job.Cfg.Defs; using Luban.Job.Cfg.Generate; using Luban.Job.Cfg.RawDefs; using Luban.Job.Cfg.Utils; using Luban.Job.Common; using Luban.Job.Common.Defs; using Luban.Job.Common.Utils; using Luban.Server.Common; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using FileInfo = Luban.Common.Protos.FileInfo; namespace Luban.Job.Cfg { [Controller("cfg")] public class JobController : IJobController { private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger(); class GenArgs : GenArgsBase { [Option("input_data_dir", Required = true, HelpText = "input data dir")] public string InputDataDir { get; set; } [Option('v', "validate_root_dir", Required = false, HelpText = "validate root directory")] public string ValidateRootDir { get; set; } [Option("output_data_dir", Required = true, HelpText = "output data directory")] public string OutputDataDir { get; set; } [Option("output_data_resource_list_file", Required = false, HelpText = "output resource list file")] public string OutputDataResourceListFile { get; set; } [Option("output_data_json_monolithic_file", Required = false, HelpText = "output monolithic json file")] public string OutputDataJsonMonolithicFile { get; set; } [Option("gen_types", Required = true, HelpText = "code_cs_bin,code_cs_json,code_cs_unity_json,code_lua_bin,code_java_bin,code_go_bin,code_go_json,code_cpp_bin,code_python27_json,code_python3_json,code_typescript_bin,code_typescript_json,data_bin,data_lua,data_json,data_json_monolithic . can be multi")] public string GenType { get; set; } [Option('s', "service", Required = true, HelpText = "service")] public string Service { get; set; } [Option("export_test_data", Required = false, HelpText = "export test data")] public bool ExportTestData { get; set; } = false; [Option('t', "l10n_timezone", Required = false, HelpText = "timezone")] public string TimeZone { get; set; } [Option("input_l10n_text_files", Required = false, HelpText = "input l10n text table files. can be multi, sep by ','")] public string InputTextTableFiles { get; set; } [Option("l10n_text_field_name", Required = false, HelpText = "text value field name of text table files. default is text")] public string TextValueFieldName { get; set; } [Option("output_l10n_not_converted_text_file", Required = false, HelpText = "the file save not converted l10n texts.")] public string OutputNotConvertTextFile { get; set; } [Option("branch", Required = false, HelpText = "branch name")] public string BranchName { get; set; } [Option("branch_input_data_dir", Required = false, HelpText = "branch input data root dir")] public string BranchInputDataDir { get; set; } } private ICfgCodeRender CreateCodeRender(string genType) { switch (genType) { case "code_cs_bin": return new CsCodeBinRender(); case "code_cs_json": return new CsCodeJsonRender(); case "code_cs_unity_json": return new CsCodeUnityJsonRender(); case "code_java_bin": return new JavaCodeBinRender(); case "code_go_bin": return new GoCodeBinRender(); case "code_go_json": return new GoCodeJsonRender(); case "code_cpp_bin": return new CppCodeBinRender(); case "code_lua_bin": return new LuaCodeBinRender(); case "code_lua_lua": return new LuaCodeLuaRender(); case "code_python27_json": return new Python27CodeJsonRender(); case "code_python3_json": return new Python3CodeJsonRender(); case "code_typescript_bin": return new TypescriptCodeBinRender(); case "code_typescript_json": return new TypescriptCodeJsonRender(); case "code_cpp_ue_editor": return new UE4EditorCppRender(); case "code_cpp_ue_bp": return new UE4BpCppRender(); case "code_cs_unity_editor": return new EditorCsRender(); default: throw new ArgumentException($"not support gen type:{genType}"); } } private ELanguage GetLanguage(string genType) { switch (genType) { case "code_cs_bin": case "code_cs_json": case "code_cs_unity_json": return ELanguage.CS; case "code_java_bin": return ELanguage.JAVA; case "code_go_bin": case "code_go_json": return ELanguage.GO; case "code_cpp_bin": return ELanguage.CPP; case "code_lua_bin": case "code_lua_lua": return ELanguage.LUA; case "code_python27_json": case "code_python3_json": return ELanguage.PYTHON; case "code_typescript_bin": case "code_typescript_json": return ELanguage.TYPESCRIPT; case "code_cpp_ue_editor": case "code_cpp_ue_bp": return ELanguage.CPP; case "code_cs_unity_editor": return ELanguage.CS; default: throw new ArgumentException($"not support output data type:{genType}"); } } private string GetOutputFileSuffix(string genType) { switch (genType) { case "data_bin": return "bin"; case "data_json": return "json"; case "data_lua": return "lua"; default: throw new Exception($"not support output data type:{genType}"); } } private string GetOutputFileName(string genType, string fileName) { return $"{(genType.EndsWith("lua") ? fileName.Replace('.', '_') : fileName)}.{GetOutputFileSuffix(genType)}"; } private static bool TryParseArg(List args, out GenArgs options, out string errMsg) { var helpWriter = new StringWriter(); var parser = new Parser(ps => { ps.HelpWriter = helpWriter; }); ; var parseResult = parser.ParseArguments(args); if (parseResult.Tag == ParserResultType.NotParsed) { errMsg = helpWriter.ToString(); options = null; return false; } else { options = (parseResult as Parsed).Value; errMsg = null; string inputDataDir = options.InputDataDir; string outputCodeDir = options.OutputCodeDir; string outputDataDir = options.OutputDataDir; var genTypes = options.GenType.Split(',').Select(s => s.Trim()).ToList(); if (genTypes.Any(t => t.StartsWith("code_", StringComparison.Ordinal)) && string.IsNullOrWhiteSpace(outputCodeDir)) { errMsg = "--outputcodedir missing"; return false; } if (genTypes.Any(t => t.StartsWith("data_", StringComparison.Ordinal))) { if (string.IsNullOrWhiteSpace(inputDataDir)) { errMsg = "--inputdatadir missing"; return false; } if (string.IsNullOrWhiteSpace(outputDataDir)) { errMsg = "--outputdatadir missing"; return false; } if (genTypes.Contains("data_resources") && string.IsNullOrWhiteSpace(options.OutputDataResourceListFile)) { errMsg = "--output_data_resource_list_file missing"; return false; } if (genTypes.Contains("output_data_json_monolithic_file") && string.IsNullOrWhiteSpace(options.OutputDataJsonMonolithicFile)) { errMsg = "--output_data_json_monolithic_file missing"; return false; } if (string.IsNullOrWhiteSpace(options.InputTextTableFiles) ^ string.IsNullOrWhiteSpace(options.OutputNotConvertTextFile)) { errMsg = "--input_l10n_text_files must be provided with --output_l10n_not_converted_text_file"; return false; } } if (string.IsNullOrWhiteSpace(options.BranchName) ^ string.IsNullOrWhiteSpace(options.BranchInputDataDir)) { errMsg = "--branch must be provided with --branch_input_data_dir"; return false; } if (!options.ValidateTypescriptRequire(options.GenType, ref errMsg)) { return false; } return true; } } class GenContext { public GenArgs GenArgs { get; init; } public DefAssembly Assembly { get; init; } public string GenType { get; set; } public ICfgCodeRender Render { get; set; } public ELanguage Lan { get; set; } public string TopModule => Assembly.TopModule; public Service TargetService => Assembly.CfgTargetService; public List ExportTypes { get; init; } public List ExportTables { get; init; } public ConcurrentBag GenCodeFilesInOutputCodeDir { get; init; } public ConcurrentBag GenDataFilesInOutputDataDir { get; init; } public ConcurrentBag GenScatteredFiles { get; init; } public List Tasks { get; init; } } public async Task GenAsync(RemoteAgent agent, GenJob rpc) { var res = new GenJobRes() { ErrCode = Luban.Common.EErrorCode.OK, ErrMsg = "succ", FileGroups = new List(), }; if (!TryParseArg(rpc.Arg.JobArguments, out GenArgs args, out string errMsg)) { res.ErrCode = Luban.Common.EErrorCode.JOB_ARGUMENT_ERROR; res.ErrMsg = errMsg; agent.Session.ReplyRpc(rpc, res); return; } var timer = new ProfileTimer(); timer.StartPhase("= gen_all ="); try { string inputDataDir = args.InputDataDir; string outputCodeDir = args.OutputCodeDir; string outputDataDir = args.OutputDataDir; var genTypes = args.GenType.Split(',').Select(s => s.Trim()).ToList(); timer.StartPhase("build defines"); var loader = new CfgDefLoader(agent); await loader.LoadAsync(args.DefineFile); await loader.LoadDefinesFromFileAsync(inputDataDir); timer.EndPhaseAndLog(); var rawDefines = loader.BuildDefines(); TimeZoneInfo timeZoneInfo = string.IsNullOrEmpty(args.TimeZone) ? null : TimeZoneInfo.FindSystemTimeZoneById(args.TimeZone); var ass = new DefAssembly(args.BranchName, timeZoneInfo, args.ExportTestData, agent) { UseUnityVectors = args.UseUnityVectors }; ass.Load(args.Service, rawDefines); DefAssemblyBase.LocalAssebmly = ass; var targetService = ass.CfgTargetService; List exportTables = ass.GetExportTables(); List exportTypes = ass.GetExportTypes(); bool hasLoadCfgData = false; bool needL10NTextConvert = !string.IsNullOrWhiteSpace(args.InputTextTableFiles); async Task CheckLoadCfgDataAsync() { if (!hasLoadCfgData) { hasLoadCfgData = true; var timer = new ProfileTimer(); timer.StartPhase("load config data"); await DataLoaderUtil.LoadCfgDataAsync(agent, ass, args.InputDataDir, args.BranchName, args.BranchInputDataDir, args.ExportTestData); timer.EndPhaseAndLog(); if (needL10NTextConvert) { ass.InitL10n(args.TextValueFieldName); await DataLoaderUtil.LoadTextTablesAsync(agent, ass, ".", args.InputTextTableFiles); } timer.StartPhase("validate"); var validateCtx = new ValidatorContext(ass, args.ValidateRootDir); await validateCtx.ValidateTables(exportTables); timer.EndPhaseAndLog(); } } var tasks = new List(); var genCodeFilesInOutputCodeDir = new ConcurrentBag(); var genDataFilesInOutputDataDir = new ConcurrentBag(); var genScatteredFiles = new ConcurrentBag(); foreach (var genType in genTypes) { var ctx = new GenContext() { GenType = genType, Assembly = ass, GenArgs = args, ExportTypes = exportTypes, ExportTables = exportTables, GenCodeFilesInOutputCodeDir = genCodeFilesInOutputCodeDir, GenDataFilesInOutputDataDir = genDataFilesInOutputDataDir, GenScatteredFiles = genScatteredFiles, Tasks = tasks, }; switch (genType) { case "code_cs_bin": case "code_cs_json": case "code_cs_unity_json": case "code_java_bin": case "code_go_bin": case "code_go_json": { GenerateCodeScatter(ctx); break; } case "code_lua_bin": case "code_lua_lua": { GenLuaCode(ctx); break; } case "code_typescript_bin": case "code_typescript_json": { GenTypescriptCode(ctx); break; } case "code_python27_json": { GenPythonCodes(ctx); break; } case "code_cpp_bin": { GenCppCode(ctx); break; } case "code_cpp_ue_editor": { GenCppUeEditor(ctx); break; } case "code_cs_unity_editor": { GenCsUnityEditor(ctx); break; } case "code_cpp_ue_bp": { GenCppUeBp(ctx); break; } case "data_bin": case "data_json": case "data_lua": { await CheckLoadCfgDataAsync(); GenDataScatter(ctx); break; } case "data_json_monolithic": { await CheckLoadCfgDataAsync(); tasks.Add(GenJsonDataMonolithic(ctx)); break; } case "data_resources": { await CheckLoadCfgDataAsync(); GenResourceList(ctx); break; } default: { s_logger.Error("unknown gentype:{gentype}", genType); break; } } } await Task.WhenAll(tasks.ToArray()); if (needL10NTextConvert) { var notConvertTextList = DataExporterUtil.GenNotConvertTextList(ass.NotConvertTextSet); var md5 = FileUtil.CalcMD5(notConvertTextList); string outputNotConvertTextFile = args.OutputNotConvertTextFile; CacheManager.Ins.AddCache(outputNotConvertTextFile, md5, notConvertTextList); genScatteredFiles.Add(new FileInfo() { FilePath = outputNotConvertTextFile, MD5 = md5 }); } if (!genCodeFilesInOutputCodeDir.IsEmpty) { res.FileGroups.Add(new FileGroup() { Dir = outputCodeDir, Files = genCodeFilesInOutputCodeDir.ToList() }); } if (!genDataFilesInOutputDataDir.IsEmpty) { res.FileGroups.Add(new FileGroup() { Dir = outputDataDir, Files = genDataFilesInOutputDataDir.ToList() }); } if (!genScatteredFiles.IsEmpty) { res.ScatteredFiles.AddRange(genScatteredFiles); } } catch (DataCreateException e) { res.ErrCode = Luban.Common.EErrorCode.DATA_PARSE_ERROR; res.ErrMsg = $@" ======================================================================= 解析失败! 文件: {e.OriginDataLocation} 错误位置: {e.DataLocationInFile} Err: {e.OriginErrorMsg} 字段: {e.VariableFullPathStr} ======================================================================= "; res.StackTrace = e.OriginStackTrace; } catch (Exception e) { res.ErrCode = Luban.Common.EErrorCode.JOB_EXCEPTION; res.ErrMsg = $@" ======================================================================= {ExceptionUtil.ExtractMessage(e)} ======================================================================= "; res.StackTrace = e.StackTrace; } DefAssemblyBase.LocalAssebmly = null; timer.EndPhaseAndLog(); agent.Session.ReplyRpc(rpc, res); } private void GenerateCodeScatter(GenContext ctx) { string genType = ctx.GenType; ctx.Render = CreateCodeRender(genType); ctx.Lan = GetLanguage(genType); foreach (var c in ctx.ExportTypes) { ctx.Tasks.Add(Task.Run(() => { var content = FileHeaderUtil.ConcatAutoGenerationHeader(ctx.Render.RenderAny(c), ctx.Lan); var file = RenderFileUtil.GetDefTypePath(c.FullName, ctx.Lan); var md5 = CacheFileUtil.GenMd5AndAddCache(file, content); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } ctx.Tasks.Add(Task.Run(() => { var module = ctx.TopModule; var name = ctx.TargetService.Manager; var content = FileHeaderUtil.ConcatAutoGenerationHeader(ctx.Render.RenderService(name, module, ctx.ExportTables), ctx.Lan); var file = RenderFileUtil.GetDefTypePath(name, ctx.Lan); var md5 = CacheFileUtil.GenMd5AndAddCache(file, content); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } private void GenerateCodeMonolithic(GenContext ctx, string outputFile, List fileContent, Action> preContent, Action> postContent) { ctx.Tasks.Add(Task.Run(() => { fileContent.Add(FileHeaderUtil.GetAutoGenerationHeader(ctx.Lan)); preContent?.Invoke(fileContent); foreach (var type in ctx.ExportTypes) { fileContent.Add(ctx.Render.RenderAny(type)); } fileContent.Add(ctx.Render.RenderService("Tables", ctx.TopModule, ctx.ExportTables)); postContent?.Invoke(fileContent); var file = outputFile; var md5 = CacheFileUtil.GenMd5AndAddCache(file, string.Join('\n', fileContent)); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } private void GenLuaCode(GenContext ctx) { string genType = ctx.GenType; LuaCodeRenderBase render = CreateCodeRender(genType) as LuaCodeRenderBase; var file = "Types.lua"; var content = render.RenderAll(ctx.ExportTypes); var md5 = CacheFileUtil.GenMd5AndAddCache(file, string.Join('\n', content)); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); } private void GenTypescriptCode(GenContext ctx) { string genType = ctx.GenType; var args = ctx.GenArgs; ctx.Render = CreateCodeRender(genType); ctx.Lan = GetLanguage(genType); var lines = new List(10000); Action> preContent = (fileContent) => { var brightRequirePath = args.TypescriptBrightRequirePath; var brightPackageName = args.TypescriptBrightPackageName; bool isGenBinary = genType.EndsWith("bin"); if (isGenBinary) { if (args.UsePuertsByteBuf) { fileContent.Add(TypescriptStringTemplate.PuertsByteBufImports); } else { fileContent.Add(TypescriptStringTemplate.GetByteBufImports(brightRequirePath, brightPackageName)); } } if (args.EmbedBrightTypes) { fileContent.Add(isGenBinary ? TypescriptStringTemplate.VectorTypesByteBuf : TypescriptStringTemplate.VectorTypesJson); if (isGenBinary) { fileContent.Add(TypescriptStringTemplate.SerializeTypes); } } else { if (isGenBinary) { fileContent.Add(TypescriptStringTemplate.GetSerializeImports(brightRequirePath, brightPackageName)); } fileContent.Add(TypescriptStringTemplate.GetVectorImports(brightRequirePath, brightPackageName)); } fileContent.Add(@$"export namespace {ctx.TopModule} {{"); }; Action> postContent = (fileContent) => { fileContent.Add("}"); // end of topmodule }; GenerateCodeMonolithic(ctx, "Types.ts", lines, preContent, postContent); } private void GenPythonCodes(GenContext ctx) { string genType = ctx.GenType; ctx.Render = CreateCodeRender(genType); ctx.Lan = GetLanguage(genType); var isPython3 = genType.Contains("python3"); var lines = new List(10000); Action> preContent = (fileContent) => { if (isPython3) { fileContent.Add(PythonStringTemplates.ImportTython3Enum); } fileContent.Add(PythonStringTemplates.PythonVectorTypes); }; GenerateCodeMonolithic(ctx, "Types.py", lines, preContent, null); ctx.Tasks.Add(Task.Run(() => { var moduleInitContent = ""; var initFile = "__init__.py"; var initMd5 = CacheFileUtil.GenMd5AndAddCache(initFile, moduleInitContent); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = initFile, MD5 = initMd5 }); })); } private void GenCppCode(GenContext ctx) { var render = new CppCodeBinRender(); // 将所有 头文件定义 生成到一个文件 // 按照 const,enum,bean,table, service 的顺序生成 ctx.Tasks.Add(Task.Run(() => { var headerFileContent = new List { @$" #pragma once #include #include ""bright/serialization/ByteBuf.h"" #include ""bright/CfgBean.hpp"" using ByteBuf = ::bright::serialization::ByteBuf; namespace {ctx.TopModule} {{ " }; foreach (var type in ctx.ExportTypes) { if (type is DefEnum e) { headerFileContent.Add(render.Render(e)); } } foreach (var type in ctx.ExportTypes) { if (type is DefConst c) { headerFileContent.Add(render.Render(c)); } } foreach (var type in ctx.ExportTypes) { if (type is DefBean e) { headerFileContent.Add(render.RenderForwardDefine(e)); } } foreach (var type in ctx.ExportTypes) { if (type is DefBean e) { headerFileContent.Add(render.Render(e)); } } foreach (var type in ctx.ExportTables) { headerFileContent.Add(render.Render(type)); } headerFileContent.Add(render.RenderService("Tables", ctx.TopModule, ctx.ExportTables)); headerFileContent.Add("}"); // end of topmodule var content = FileHeaderUtil.ConcatAutoGenerationHeader(string.Join('\n', headerFileContent), ELanguage.CPP); var file = "gen_types.h"; var md5 = CacheFileUtil.GenMd5AndAddCache(file, content, true); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); var beanTypes = ctx.ExportTypes.Where(c => c is DefBean).ToList(); int TYPE_PER_STUB_FILE = 100; for (int i = 0, n = (beanTypes.Count + TYPE_PER_STUB_FILE - 1) / TYPE_PER_STUB_FILE; i < n; i++) { int index = i; ctx.Tasks.Add(Task.Run(() => { int startIndex = index * TYPE_PER_STUB_FILE; var content = FileHeaderUtil.ConcatAutoGenerationHeader( render.RenderStub(ctx.TopModule, beanTypes.GetRange(startIndex, Math.Min(TYPE_PER_STUB_FILE, beanTypes.Count - startIndex))), ELanguage.CPP); var file = $"gen_stub_{index}.cpp"; var md5 = CacheFileUtil.GenMd5AndAddCache(file, content, true); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } } private void GenCppUeEditor(GenContext ctx) { var render = new UE4EditorCppRender(); var renderTypes = ctx.Assembly.Types.Values.Where(c => c is DefEnum || c is DefBean).ToList(); foreach (var c in renderTypes) { ctx.Tasks.Add(Task.Run(() => { var content = FileHeaderUtil.ConcatAutoGenerationHeader(render.RenderAny(c), ELanguage.CPP); var file = "editor_" + RenderFileUtil.GetUeCppDefTypeHeaderFilePath(c.FullName); var md5 = CacheFileUtil.GenMd5AndAddCache(file, content, true); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } int TYPE_PER_STUB_FILE = 200; for (int i = 0, n = (renderTypes.Count + TYPE_PER_STUB_FILE - 1) / TYPE_PER_STUB_FILE; i < n; i++) { int index = i; ctx.Tasks.Add(Task.Run(() => { int startIndex = index * TYPE_PER_STUB_FILE; var content = FileHeaderUtil.ConcatAutoGenerationHeader( render.RenderStub(renderTypes.GetRange(startIndex, Math.Min(TYPE_PER_STUB_FILE, renderTypes.Count - startIndex))), ELanguage.CPP); var file = $"stub_{index}.cpp"; var md5 = CacheFileUtil.GenMd5AndAddCache(file, content, true); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } } private void GenCsUnityEditor(GenContext ctx) { var render = new EditorCsRender(); foreach (var c in ctx.Assembly.Types.Values) { ctx.Tasks.Add(Task.Run(() => { var content = FileHeaderUtil.ConcatAutoGenerationHeader(render.RenderAny(c), ELanguage.CS); var file = RenderFileUtil.GetDefTypePath(c.FullName, ELanguage.CS); var md5 = CacheFileUtil.GenMd5AndAddCache(file, content); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } } private void GenCppUeBp(GenContext ctx) { var render = new UE4BpCppRender(); foreach (var c in ctx.ExportTypes) { if (!(c is DefEnum || c is DefBean)) { continue; } ctx.Tasks.Add(Task.Run(() => { var content = FileHeaderUtil.ConcatAutoGenerationHeader(render.RenderAny(c), ELanguage.CPP); var file = "bp_" + RenderFileUtil.GetUeCppDefTypeHeaderFilePath(c.FullName); var md5 = CacheFileUtil.GenMd5AndAddCache(file, content, true); ctx.GenCodeFilesInOutputCodeDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } } private void GenDataScatter(GenContext ctx) { string genType = ctx.GenType; foreach (var table in ctx.ExportTables) { ctx.Tasks.Add(Task.Run(() => { var file = GetOutputFileName(genType, table.OutputDataFile); var records = ctx.Assembly.GetTableExportDataList(table); if (!FileRecordCacheManager.Ins.TryGetRecordOutputData(table, records, genType, out string md5)) { var content = DataExporterUtil.ToOutputData(table, records, genType); md5 = CacheFileUtil.GenStringOrBytesMd5AndAddCache(file, content); FileRecordCacheManager.Ins.AddCachedRecordOutputData(table, records, genType, md5); } ctx.GenDataFilesInOutputDataDir.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } } private async Task GenJsonDataMonolithic(GenContext ctx) { var exportTables = ctx.ExportTables; var allJsonTask = new List>(); foreach (var c in exportTables) { allJsonTask.Add(Task.Run(() => { return (string)DataExporterUtil.ToOutputData(c, ctx.Assembly.GetTableExportDataList(c), "data_json"); })); } var lines = new List(); lines.Add("{"); for (int i = 0; i < exportTables.Count; i++) { if (i != 0) { lines.Add(","); } lines.Add($"\"{exportTables[i].NeedExport}\":"); lines.Add(await allJsonTask[i]); } lines.Add("}"); var content = string.Join('\n', lines); var outputFile = ctx.GenArgs.OutputDataJsonMonolithicFile; var md5 = CacheFileUtil.GenMd5AndAddCache(outputFile, content); ctx.GenScatteredFiles.Add(new FileInfo() { FilePath = outputFile, MD5 = md5 }); } private void GenResourceList(GenContext ctx) { var genDataTasks = new List>>(); foreach (var c in ctx.ExportTables) { genDataTasks.Add(Task.Run(() => { return DataExporterUtil.ExportResourceList(ctx.Assembly.GetTableExportDataList(c)); })); } ctx.Tasks.Add(Task.Run(async () => { var ress = new HashSet<(string, string)>(10000); var resourceLines = new List(10000); foreach (var task in genDataTasks) { foreach (var ri in await task) { if (ress.Add((ri.Resource, ri.Tag))) { resourceLines.Add($"{ri.Resource},{ri.Tag}"); } } } var file = ctx.GenArgs.OutputDataResourceListFile; var content = string.Join("\n", resourceLines); var md5 = CacheFileUtil.GenMd5AndAddCache(file, content); ctx.GenScatteredFiles.Add(new FileInfo() { FilePath = file, MD5 = md5 }); })); } } }