using Bright.Collections; using Luban.Common.Utils; using Luban.Job.Cfg.Datas; using Luban.Job.Cfg.DataVisitors; using Luban.Job.Cfg.Defs; using Luban.Job.Cfg.RawDefs; using Luban.Job.Cfg.Validators; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Luban.Job.Cfg { public class PathQuery { public PathValidator Validator { get; set; } public string DataPath { get; set; } public string Value { get; set; } public string Source { get; set; } public object QueryPath { get; set; } } public class ValidatorContext { private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger(); [ThreadStatic] private static ValidatorVisitor t_visitor; public static ValidatorVisitor CurrentVisitor { get => t_visitor; set => t_visitor = value; } public static string CurrentRecordPath => TypeUtil.MakeFullName(CurrentVisitor.Path); public DefAssembly Assembly { get; } public string RootDir { get; } private readonly List _pathQuerys = new List(1000); public void AddPathQuery(PathQuery query) { lock (this) { _pathQuerys.Add(query); } } public List GetPathQueries() => _pathQuerys; public ValidatorContext(DefAssembly ass, string rootDir) { this.Assembly = ass; this.RootDir = rootDir; } public async Task ValidateTables(IEnumerable tables) { { var tasks = new List(); foreach (var t in tables) { tasks.Add(Task.Run(() => { ValidateTableModeIndex(t); })); } await Task.WhenAll(tasks); } { var tasks = new List(); foreach (var t in tables) { tasks.Add(Task.Run(() => { var records = t.Assembly.GetTableAllDataList(t); var visitor = new ValidatorVisitor(this); try { CurrentVisitor = visitor; visitor.ValidateTable(t, records); #if !LUBAN_LITE if (this.Assembly.NeedL10nTextTranslate) { ValidateText(t, records); } #endif } finally { CurrentVisitor = null; } })); } await Task.WhenAll(tasks); } if (!string.IsNullOrWhiteSpace(RootDir)) { await ValidatePaths(); } } #if !LUBAN_LITE private void ValidateText(DefTable table, List records) { foreach (var r in records) { CurrentVisitor.CurrentValidateRecord = r; r.Data.Apply(TextValidatorVisitor.Ins, this.Assembly.RawTextTable); } CurrentVisitor.CurrentValidateRecord = null; } #endif private async Task ValidatePaths() { var queryFiles = new HashSet(_pathQuerys.Count * 2); foreach (var q in _pathQuerys) { switch (q.QueryPath) { case string s: queryFiles.Add(s); break; case List ls: { foreach (var p in ls) { queryFiles.Add(p); } break; } default: throw new NotSupportedException(); } } var files = new List(queryFiles); var res = await this.Assembly.Agent.QueryFileExistsAsync(new Luban.Common.Protos.QueryFilesExistsArg() { Root = RootDir, Files = files }); var fileNotExistsSet = new HashSet(queryFiles.Count * 2); for (int i = 0; i < files.Count; i++) { if (!res.Exists[i]) { fileNotExistsSet.Add(files[i]); } } var agent = this.Assembly.Agent; foreach (var q in _pathQuerys) { switch (q.QueryPath) { case string s: { if (fileNotExistsSet.Contains(s)) { agent.Error("记录 {0} = {1} (来自文件:{2}) 所引用文件:{3} 不存在", q.DataPath, q.Value, q.Source, s); } break; } case List ls: { if (ls.All(f => fileNotExistsSet.Contains(f))) { #if !LUBAN_LITE agent.Error("记录 {0} = {1} (来自文件:{2}) 所引用文件:{3} 不存在", q.DataPath, q.Value, q.Source, string.Join(',', ls)); #else agent.Error("记录 {0} = {1} (来自文件:{2}) 所引用文件:{3} 不存在", q.DataPath, q.Value, q.Source, string.Join(",", ls)); #endif } break; } default: throw new NotSupportedException(); } } } private void ValidateTableModeIndex(DefTable table) { var tableDataInfo = Assembly.GetTableDataInfo(table); List mainRecords = tableDataInfo.MainRecords; List patchRecords = tableDataInfo.PatchRecords; // 这么大费周张是为了保证被覆盖的id仍然保持原来的顺序,而不是出现在最后 int index = 0; foreach (var r in mainRecords) { r.Index = index++; } if (patchRecords != null) { foreach (var r in patchRecords) { r.Index = index++; } } var mainRecordMap = new Dictionary(); switch (table.Mode) { case ETableMode.ONE: { //if (mainRecords.Count != 1) //{ // throw new Exception($"配置表 {table.FullName} 是单值表 mode=one,但主文件数据个数:{mainRecords.Count} != 1"); //} //if (patchRecords != null && patchRecords.Count != 1) //{ // throw new Exception($"配置表 {table.FullName} 是单值表 mode=one,但分支文件数据个数:{patchRecords.Count} != 1"); //} if (patchRecords != null) { mainRecords = patchRecords; } break; } case ETableMode.MAP: { foreach (Record r in mainRecords) { DType key = r.Data.Fields[table.IndexFieldIdIndex]; if (!mainRecordMap.TryAdd(key, r)) { throw new Exception($@"配置表 '{table.FullName}' 主文件 主键字段:'{table.Index}' 主键值:'{key}' 重复. 记录1 来自文件:{r.Source} 记录2 来自文件:{mainRecordMap[key].Source} "); } } if (patchRecords != null) { var patchRecordMap = new Dictionary(); foreach (Record r in patchRecords) { DType key = r.Data.Fields[table.IndexFieldIdIndex]; if (!patchRecordMap.TryAdd(key, r)) { throw new Exception($@"配置表 '{table.FullName}' 分支文件 主键字段:'{table.Index}' 主键值:'{key}' 重复. 记录1 来自文件:{r.Source} 记录2 来自文件:{patchRecordMap[key].Source} "); } if (mainRecordMap.TryGetValue(key, out var old)) { s_logger.Debug("配置表 {} 分支文件 主键:{} 覆盖 主文件记录", table.FullName, key); mainRecords[old.Index] = r; } mainRecordMap[key] = r; } } break; } } #if !LUBAN_LITE tableDataInfo.FinalRecords = mainRecords; tableDataInfo.FinalRecordMap = mainRecordMap; #endif } } }