using CommandLine; using Luban.Common.Protos; using Luban.Common.Utils; using Luban.Job.Cfg.DataCreators; using Luban.Job.Cfg.Defs; using Luban.Job.Cfg.Generate; using Luban.Job.Cfg.Utils; 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.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(); 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.L10nInputTextTableFiles) ^ string.IsNullOrWhiteSpace(options.L10nOutputNotTranslatedTextFile)) { errMsg = "--input_l10n_text_files must be provided with --output_l10n_not_translated_text_file"; return false; } if (genTypes.Contains("data_template") ^ !string.IsNullOrWhiteSpace(options.TemplateDataFile)) { errMsg = "gen_types data_template should use with --template:data:file"; return false; } if (genTypes.Contains("convert_template") ^ !string.IsNullOrWhiteSpace(options.TemplateConvertFile)) { errMsg = "gen_types convert_template should use with --template:convert:file"; return false; } if (genTypes.Contains("code_template") ^ !string.IsNullOrWhiteSpace(options.TemplateCodeDir)) { errMsg = "gen_types code_template should use with --template:code:dir"; return false; } } if (string.IsNullOrWhiteSpace(options.L10nPatchName) ^ string.IsNullOrWhiteSpace(options.L10nPatchInputDataDir)) { errMsg = "--patch must be provided with --patch_input_data_dir"; return false; } if (options.GenType.Contains("typescript_bin") && !options.ValidateTypescriptRequire(options.GenType, ref errMsg)) { return false; } if (options.GenType.Contains("go_") && !options.ValidateGoRequire(options.GenType, ref errMsg)) { return false; } if (!options.ValidateConvention(ref errMsg)) { return false; } if (options.GenType.Contains("unity")) { options.CsUseUnityVectors = true; } return true; } } 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.L10nTimeZone) ? null : TimeZoneInfo.FindSystemTimeZoneById(args.L10nTimeZone); var excludeTags = args.OutputExcludeTags.Split(',').Select(t => t.Trim().ToLowerInvariant()).Where(t => !string.IsNullOrEmpty(t)).ToList(); var ass = new DefAssembly(args.L10nPatchName, timeZoneInfo, excludeTags, agent); ass.Load(rawDefines, agent, args); List exportTables = ass.GetExportTables(); List exportTypes = ass.GetExportTypes(); bool hasLoadCfgData = false; bool needL10NTextConvert = !string.IsNullOrWhiteSpace(args.L10nInputTextTableFiles); async Task CheckLoadCfgDataAsync() { if (!hasLoadCfgData) { hasLoadCfgData = true; var timer = new ProfileTimer(); timer.StartPhase("load config data"); await DataLoaderUtil.LoadCfgDataAsync(agent, ass, args.InputDataDir, args.L10nPatchName, args.L10nPatchInputDataDir, args.InputConvertDataDir); timer.EndPhaseAndLog(); if (needL10NTextConvert) { ass.InitL10n(args.L10nTextValueFieldName); await DataLoaderUtil.LoadTextTablesAsync(agent, ass, ".", args.L10nInputTextTableFiles); } timer.StartPhase("validate"); var validateCtx = new ValidatorContext(ass, args.ValidateRootDir); await validateCtx.ValidateTables(ass.GetAllTables()); 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, DataLoader = CheckLoadCfgDataAsync, }; GenContext.Ctx = ctx; var render = RenderFactory.CreateRender(genType); if (render == null) { throw new Exception($"unknown gentype:{genType}"); } if (render is DataRenderBase) { await CheckLoadCfgDataAsync(); } render.Render(ctx); GenContext.Ctx = null; } await Task.WhenAll(tasks.ToArray()); if (needL10NTextConvert) { var notConvertTextList = DataExporterUtil.GenNotConvertTextList(ass.NotConvertTextSet); var md5 = FileUtil.CalcMD5(notConvertTextList); string outputNotConvertTextFile = args.L10nOutputNotTranslatedTextFile; 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); } } }