using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using UnityEditor; using UnityEngine; using OfficeOpenXml; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Thrift.Protocol; using Thrift.Transport.Client; using Debug = UnityEngine.Debug; namespace ThriftPipelineTools { /// /// 导入 Docs 配置表工具 /// 完全复刻 integrated_pipeline.py 的功能 /// 从 Excel 到 Unity 的一站式处理 /// public class ThriftIntegratedPipelineEditor : EditorWindow { // ========== 版本信息 ========== private const string VERSION = "v1.0.0 (C# Edition)"; // ========== 路径配置 ========== private string docsProjectPath = ""; // Docs 项目路径 private string thriftProjectPath = ""; // Thrift 项目路径 // ========== UI 状态 ========== private Vector2 scrollPosition; private Vector2 logScrollPosition; private StringBuilder logBuilder = new StringBuilder(); private bool isExecuting = false; // ========== 进度控制 ========== private float progress = 0f; private string currentStep = ""; private int totalSteps = 7; private int completedSteps = 0; // ========== 报告数据 ========== private List reportSteps = new List(); private DateTime startTime; private DateTime endTime; // ========== 配置Key(使用EditorPrefs) ========== private const string PREF_KEY_DOCS_PATH = "ThriftPipeline_DocsPath"; private const string PREF_KEY_THRIFT_PATH = "ThriftPipeline_ThriftPath"; // ========== 临时数据 ========== private List configStructNames = new List(); [MenuItem("配置表pipeline/Thrift/生成DR文件")] public static void ShowWindow() { var window = GetWindow("导入 Docs 配置表工具"); window.minSize = new Vector2(1000, 800); window.Show(); } private void OnEnable() { LoadConfig(); } private void OnGUI() { scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); EditorGUILayout.Space(10); EditorGUILayout.LabelField($"导入 Docs 配置表工具 - {VERSION}", EditorStyles.boldLabel); EditorGUILayout.HelpBox( "此工具完全复刻 integrated_pipeline.py 的功能\n" + "从 Excel 配置文件生成 Thrift 定义、编译为 C# 代码、生成二进制数据、创建 DataRow 类\n" + "一键完成整个流程", MessageType.Info ); EditorGUILayout.Space(10); DrawPathConfiguration(); EditorGUILayout.Space(10); DrawExecutionSection(); EditorGUILayout.Space(10); DrawLogSection(); EditorGUILayout.EndScrollView(); } /// /// 绘制路径配置区域 /// private void DrawPathConfiguration() { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("路径配置", EditorStyles.boldLabel); EditorGUILayout.HelpBox( "配置两个基础路径,其他路径将自动推导\n" + "- Docs 项目路径: 包含 Excel 配置和 cfg_txt.json\n" + "- Thrift 项目路径: 包含 thrift.exe 编译器和 thrift 文件目录", MessageType.None ); // Docs 项目路径 EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Docs 项目路径:", GUILayout.Width(120)); docsProjectPath = EditorGUILayout.TextField(docsProjectPath); if (GUILayout.Button("选择", GUILayout.Width(60))) { string selected = EditorUtility.OpenFolderPanel("选择 Docs 项目路径", docsProjectPath, ""); if (!string.IsNullOrEmpty(selected)) { docsProjectPath = selected; SaveConfig(); } } EditorGUILayout.EndHorizontal(); // Thrift 项目路径 EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Thrift 项目路径:", GUILayout.Width(120)); thriftProjectPath = EditorGUILayout.TextField(thriftProjectPath); if (GUILayout.Button("选择", GUILayout.Width(60))) { string selected = EditorUtility.OpenFolderPanel("选择 Thrift 项目路径", thriftProjectPath, ""); if (!string.IsNullOrEmpty(selected)) { thriftProjectPath = selected; SaveConfig(); } } EditorGUILayout.EndHorizontal(); // 显示派生路径预览 if (!string.IsNullOrEmpty(docsProjectPath) && !string.IsNullOrEmpty(thriftProjectPath)) { EditorGUILayout.Space(5); EditorGUILayout.LabelField("派生路径预览:", EditorStyles.miniLabel); var paths = GetDerivedPaths(); EditorGUILayout.LabelField($" cfg_txt.json: {paths["cfg_json"]}", EditorStyles.miniLabel); EditorGUILayout.LabelField($" Config 目录: {paths["config_dir"]}", EditorStyles.miniLabel); EditorGUILayout.LabelField($" Thrift 编译器: {paths["compiler"]}", EditorStyles.miniLabel); EditorGUILayout.LabelField($" 输出到 Unity C#: {paths["unity_csharp_dir"]}", EditorStyles.miniLabel); EditorGUILayout.LabelField($" 输出到 Unity Bytes: {paths["unity_bytes_dir"]}", EditorStyles.miniLabel); EditorGUILayout.LabelField($" 输出到 Unity DR: {paths["dr_output_dir"]}", EditorStyles.miniLabel); } EditorGUILayout.EndVertical(); } /// /// 绘制执行区域 /// private void DrawExecutionSection() { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("执行控制", EditorStyles.boldLabel); GUI.enabled = !isExecuting && ValidatePaths(); if (GUILayout.Button("🚀 开始执行完整流程", GUILayout.Height(40))) { ExecutePipeline(); } GUI.enabled = true; if (isExecuting) { EditorGUILayout.Space(5); EditorGUILayout.LabelField($"当前步骤: {currentStep}"); EditorGUI.ProgressBar(EditorGUILayout.GetControlRect(GUILayout.Height(20)), progress, $"{completedSteps}/{totalSteps}"); } EditorGUILayout.EndVertical(); } /// /// 绘制日志区域 /// private void DrawLogSection() { EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("执行日志", EditorStyles.boldLabel); logScrollPosition = EditorGUILayout.BeginScrollView( logScrollPosition, GUILayout.Height(300) ); EditorGUILayout.TextArea(logBuilder.ToString(), GUILayout.ExpandHeight(true)); EditorGUILayout.EndScrollView(); if (GUILayout.Button("清空日志")) { logBuilder.Clear(); } EditorGUILayout.EndVertical(); } /// /// 加载配置(使用 EditorPrefs,本地保存,不会被git提交) /// private void LoadConfig() { try { // 从 EditorPrefs 读取路径(每台电脑独立保存) docsProjectPath = EditorPrefs.GetString(PREF_KEY_DOCS_PATH, ""); thriftProjectPath = EditorPrefs.GetString(PREF_KEY_THRIFT_PATH, ""); } catch (Exception ex) { Debug.LogError($"加载配置失败: {ex.Message}"); } } /// /// 保存配置(使用 EditorPrefs,本地保存,不会被git提交) /// private void SaveConfig() { try { // 保存到 EditorPrefs(每台电脑独立保存,不会被git追踪) EditorPrefs.SetString(PREF_KEY_DOCS_PATH, docsProjectPath); EditorPrefs.SetString(PREF_KEY_THRIFT_PATH, thriftProjectPath); } catch (Exception ex) { Debug.LogError($"保存配置失败: {ex.Message}"); } } /// /// 获取所有派生路径 /// private Dictionary GetDerivedPaths() { var paths = new Dictionary(); // Docs 相关路径 if (!string.IsNullOrEmpty(docsProjectPath)) { paths["cfg_json"] = Path.Combine(docsProjectPath, "tool", "cfg", "cfg_txt.json"); paths["config_dir"] = Path.Combine(docsProjectPath, "config"); } // Thrift 项目相关路径(包含中间输出目录) if (!string.IsNullOrEmpty(thriftProjectPath)) { paths["thrift_project_path"] = thriftProjectPath; // 添加项目根路径供Python脚本使用 paths["compiler"] = Path.Combine(thriftProjectPath, "compiler", "exe", "thrift.exe"); paths["thrift_dir"] = Path.Combine(thriftProjectPath, "thrift-files", "meowment"); paths["csharp_output_dir"] = Path.Combine(thriftProjectPath, "compiled_output", "csharp"); paths["bytes_output_dir"] = Path.Combine(thriftProjectPath, "binary_output"); } // Unity 项目相关路径(当前项目) string assetsPath = Application.dataPath; paths["unity_csharp_dir"] = Path.Combine(assetsPath, "Design_SubModule", "Scripts", "thrift", "gen-netstd"); paths["unity_bytes_dir"] = Path.Combine(assetsPath, "Design_SubModule", "ConfigData"); paths["dr_output_dir"] = Path.Combine(assetsPath, "Design_SubModule", "Scripts", "DR_Generated"); return paths; } /// /// 验证路径 /// private bool ValidatePaths() { if (string.IsNullOrEmpty(docsProjectPath) || string.IsNullOrEmpty(thriftProjectPath)) return false; var paths = GetDerivedPaths(); if (!File.Exists(paths["cfg_json"])) return false; if (!Directory.Exists(paths["config_dir"])) return false; if (!File.Exists(paths["compiler"])) return false; return true; } /// /// 日志输出 /// private void Log(string message) { logBuilder.AppendLine(message); Repaint(); } /// /// 添加步骤报告 /// private void AddStepReport(string stepName, bool success, Dictionary details = null) { reportSteps.Add(new StepReport { Name = stepName, Success = success, Time = DateTime.Now, Details = details ?? new Dictionary() }); } /// /// 更新进度 /// private void UpdateProgress(int step, string stepName) { completedSteps = step; currentStep = stepName; progress = (float)completedSteps / totalSteps; Repaint(); } /// /// 保存详细日志(失败也会保存) /// private void SaveDetailedLog(string reason, Exception ex = null) { try { if (endTime == default) { endTime = DateTime.Now; } string logDir = Path.Combine( Application.dataPath, "Design_SubModule", "Scripts", "Editor", "ThriftPipelineTool", "Log" ); if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } string logFileName = $"ThriftPipeline_Fail_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; string logFilePath = Path.Combine(logDir, logFileName); StringBuilder fullLog = new StringBuilder(); fullLog.AppendLine("================================================================================"); fullLog.AppendLine(" Thrift 流程故障日志"); fullLog.AppendLine("================================================================================"); fullLog.AppendLine($"原因: {reason}"); fullLog.AppendLine($"当前步骤: {currentStep}"); fullLog.AppendLine($"进度: {completedSteps}/{totalSteps} ({progress:P0})"); fullLog.AppendLine($"执行时间: {startTime:yyyy-MM-dd HH:mm:ss} -> {endTime:yyyy-MM-dd HH:mm:ss}"); fullLog.AppendLine($"机器: {Environment.MachineName}"); fullLog.AppendLine($"用户: {Environment.UserName}"); fullLog.AppendLine($"系统: {Environment.OSVersion}"); fullLog.AppendLine($"Unity: {Application.unityVersion}"); fullLog.AppendLine($"项目: {Application.dataPath}"); fullLog.AppendLine(); if (ex != null) { fullLog.AppendLine("--------------------------------------------------------------------------------"); fullLog.AppendLine("异常信息:"); fullLog.AppendLine(ex.Message); fullLog.AppendLine(ex.StackTrace); fullLog.AppendLine(); } fullLog.AppendLine("--------------------------------------------------------------------------------"); fullLog.AppendLine("窗口日志快照:"); fullLog.AppendLine(logBuilder.ToString()); fullLog.AppendLine(); try { string report = GenerateReport(); fullLog.AppendLine(report); } catch (Exception reportEx) { fullLog.AppendLine("--------------------------------------------------------------------------------"); fullLog.AppendLine("报告生成失败:"); fullLog.AppendLine(reportEx.Message); fullLog.AppendLine(reportEx.StackTrace); } File.WriteAllText(logFilePath, fullLog.ToString()); Log($"\n失败日志已保存: Log/{logFileName}"); } catch (Exception logEx) { Debug.LogError($"保存失败日志异常: {logEx.Message}\n{logEx.StackTrace}"); } } /// /// 执行完整流程 /// private async void ExecutePipeline() { isExecuting = true; logBuilder.Clear(); reportSteps.Clear(); completedSteps = 0; startTime = DateTime.Now; Log("================================================================================"); Log($"开始执行完整流程 ({VERSION})"); Log("================================================================================"); Log(""); try { var paths = GetDerivedPaths(); // 步骤1: 生成 Thrift 文件 UpdateProgress(0, "步骤1: 生成 Thrift 文件"); if (!await Step1_GenerateThriftFiles(paths)) { Log("[FAIL] 步骤1失败,终止执行"); SaveDetailedLog("步骤1失败"); return; } // 步骤2: 编译 Thrift 到 C# UpdateProgress(1, "步骤2: 编译 Thrift 到 C#"); if (!await Step2_CompileToCSharp(paths)) { Log("[FAIL] 步骤2失败,终止执行"); SaveDetailedLog("步骤2失败"); return; } // 步骤3: 生成 Bytes 文件 UpdateProgress(2, "步骤3: 生成 Bytes 文件"); if (!await Step3_GenerateBytes(paths)) { Log("[FAIL] 步骤3失败,终止执行"); SaveDetailedLog("步骤3失败"); return; } // 步骤4: 清理冲突文件 UpdateProgress(3, "步骤4: 清理冲突文件"); if (!Step4_CleanupExtensions(paths)) { Log("[FAIL] 步骤4失败,终止执行"); SaveDetailedLog("步骤4失败"); return; } // 步骤5: 复制 C# 到 Unity UpdateProgress(4, "步骤5: 复制 C# 到 Unity"); if (!Step5_CopyCSharp(paths)) { Log("[FAIL] 步骤5失败,终止执行"); SaveDetailedLog("步骤5失败"); return; } // 步骤6: 复制 Bytes 到 Unity UpdateProgress(5, "步骤6: 复制 Bytes 到 Unity"); if (!Step6_CopyBytes(paths)) { Log("[FAIL] 步骤6失败,终止执行"); SaveDetailedLog("步骤6失败"); return; } // 步骤7: 生成 DR 文件 UpdateProgress(6, "步骤7: 生成 DR 文件"); if (!Step7_GenerateDR(paths)) { Log("[FAIL] 步骤7失败,终止执行"); SaveDetailedLog("步骤7失败"); return; } UpdateProgress(7, "完成"); endTime = DateTime.Now; // 生成报告 string report = GenerateReport(); Log("\n" + report); // 保存完整日志到 ThriftPipeline/Log 目录 string logDir = Path.Combine( Application.dataPath, "Editor", "CustomTools", "ThriftPipeline", "Log" ); // 确保Log目录存在 if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } // 保存完整日志(详细日志 + 摘要报告) string logFileName = $"ThriftPipeline_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; string logFilePath = Path.Combine(logDir, logFileName); StringBuilder fullLog = new StringBuilder(); fullLog.AppendLine("================================================================================"); fullLog.AppendLine(" 详细执行日志"); fullLog.AppendLine("================================================================================"); fullLog.AppendLine(); fullLog.Append(logBuilder.ToString()); fullLog.AppendLine(); fullLog.AppendLine(); fullLog.Append(report); File.WriteAllText(logFilePath, fullLog.ToString()); Log($"\n日志已保存: Log/{logFileName}"); EditorUtility.DisplayDialog("成功", "流程执行完成!", "确定"); } catch (Exception ex) { Log($"\n[FAIL] 执行失败: {ex.Message}"); Log(ex.StackTrace); SaveDetailedLog("执行异常", ex); EditorUtility.DisplayDialog("错误", $"执行失败:\n{ex.Message}", "确定"); } finally { isExecuting = false; AssetDatabase.Refresh(); Repaint(); } } // ========== 步骤1: 生成 Thrift 文件 ========== private async Task Step1_GenerateThriftFiles(Dictionary paths) { Log("\n【步骤1】生成 Thrift 文件"); Log("--------------------------------------------------------------------------------"); try { string cfgJsonPath = paths["cfg_json"]; string configDir = paths["config_dir"]; string thriftDir = paths["thrift_dir"]; // 读取 cfg_txt.json string cfgText = File.ReadAllText(cfgJsonPath); var cfg = JsonConvert.DeserializeObject(cfgText); var fileList = cfg["file_list"] as JArray; if (fileList == null || fileList.Count == 0) { Log("[FAIL] cfg_txt.json 中没有文件配置"); AddStepReport("生成Thrift文件", false); return false; } // 创建 thrift 目录 if (!Directory.Exists(thriftDir)) Directory.CreateDirectory(thriftDir); int generatedCount = 0; List structNames = new List(); foreach (JObject configItem in fileList) { string inFile = configItem["in_file"]?.ToString(); string outFile = configItem["out_file"]?.ToString(); // 用这个生成 thrift 名称 string sheetName = configItem["sheet_name"]?.ToString(); var columnTypes = configItem["coloum_type"] as JArray; if (string.IsNullOrEmpty(inFile) || string.IsNullOrEmpty(outFile)) continue; // 从 out_file 获取结构名(与 Python 版本一致) string structName = outFile.Replace(".txt", ""); string excelPath = Path.Combine(configDir, inFile); if (!File.Exists(excelPath)) { Log($" [SKIP] Excel 文件不存在: {inFile}"); continue; } Log($" 处理: {inFile} -> {structName}"); // 读取 Excel(如果有 sheet_name 就用,否则用第一个 sheet) var (headers, types, comments) = ReadExcelHeaders(excelPath, sheetName, columnTypes); if (headers == null || headers.Count == 0) { Log($" [SKIP] 无法读取表头: {structName}"); continue; } // 生成 thrift 内容 string thriftContent = GenerateThriftContent(structName, headers, types, comments); // 写入文件 string thriftFilePath = Path.Combine(thriftDir, $"{structName}.thrift"); File.WriteAllText(thriftFilePath, thriftContent); structNames.Add(structName); generatedCount++; Log($" [OK] 生成: {structName}.thrift"); } // 生成 AllConfigs.thrift if (structNames.Count > 0) { string allConfigsContent = GenerateAllConfigsContent(structNames); string allConfigsPath = Path.Combine(thriftDir, "AllConfigs.thrift"); File.WriteAllText(allConfigsPath, allConfigsContent); Log($" [OK] 生成: AllConfigs.thrift (包含 {structNames.Count} 个配置)"); } // 保存结构名列表供后续步骤使用 configStructNames = structNames; Log($"\n生成完成: {generatedCount} 个 thrift 文件"); AddStepReport("生成Thrift文件", true, new Dictionary { { "total", generatedCount }, { "files", structNames } }); return true; } catch (Exception ex) { Log($"[FAIL] 步骤1失败: {ex.Message}"); Log(ex.StackTrace); AddStepReport("生成Thrift文件", false, new Dictionary { { "error", ex.Message } }); return false; } } /// /// 读取 Excel 表头、类型和注释 /// 完全复刻 Python 的读取逻辑:跳过空表头列,而不是停止 /// private (List headers, List types, List comments) ReadExcelHeaders( string excelPath, string sheetName, JArray columnTypes) { try { FileInfo fileInfo = new FileInfo(excelPath); using (ExcelPackage package = new ExcelPackage(fileInfo)) { // 如果指定了 sheet_name 就用,否则用第一个 sheet(与 Python 版本一致) ExcelWorksheet worksheet = null; if (!string.IsNullOrEmpty(sheetName) && package.Workbook.Worksheets[sheetName] != null) { worksheet = package.Workbook.Worksheets[sheetName]; } else { worksheet = package.Workbook.Worksheets[0]; } if (worksheet == null || worksheet.Dimension == null) { Log($" [WARN] 工作表 '{sheetName}' 不存在或为空"); return (null, null, null); } int maxCols = worksheet.Dimension.End.Column; List headers = new List(); List types = new List(); List comments = new List(); // 只读取 coloum_types 定义的数量(与 Python 版本完全一致) int maxColumns = columnTypes != null && columnTypes.Count > 0 ? columnTypes.Count : maxCols; // Excel 结构(与 Python 版本完全一致): // 第1行:字段名(表头) // 第2行:注释说明 // 第3行开始:数据 // EPPlus 索引从 1 开始 for (int col = 1; col <= maxCols; col++) { // 读取字段名(第1行) string header = worksheet.Cells[1, col].Value?.ToString() ?? ""; // 重要:如果字段名为空,跳过这一列,而不是停止(与 Python 版本一致) if (string.IsNullOrWhiteSpace(header)) { continue; } headers.Add(header); // 读取注释(第2行) string comment = worksheet.Cells[2, col].Value?.ToString() ?? ""; comments.Add(comment); // 读取类型(从 cfg_txt.json 的 coloum_type) int typeIndex = headers.Count - 1; if (columnTypes != null && typeIndex < columnTypes.Count) { types.Add(columnTypes[typeIndex].ToString()); } else { types.Add("string"); // 默认类型 } // 达到 coloum_types 定义的数量后停止(关键逻辑) if (headers.Count >= maxColumns) { break; } } Log($" 读取到 {headers.Count} 个有效列"); return (headers, types, comments); } } catch (Exception ex) { Log($" [ERROR] 读取 Excel 失败: {ex.Message}"); return (null, null, null); } } /// /// 生成 Thrift 文件内容 /// private string GenerateThriftContent(string structName, List headers, List types, List comments) { StringBuilder sb = new StringBuilder(); sb.AppendLine("namespace netstd Byway.Thrift.Data"); sb.AppendLine(); sb.AppendLine($"struct {structName}Item"); sb.AppendLine("{"); // 类型映射 Dictionary typeMap = new Dictionary { { "int", "i32" }, { "int32", "i32" }, { "i32", "i32" }, { "integer", "i32" }, { "long", "i64" }, { "int64", "i64" }, { "i64", "i64" }, { "float", "double" }, { "double", "double" }, { "string", "string" }, { "str", "string" }, { "text", "string" }, { "bool", "bool" }, { "boolean", "bool" }, { "i8", "i8" }, { "i16", "i16" } }; for (int i = 0; i < headers.Count; i++) { string header = SanitizeFieldName(headers[i]); // 清理字段名 string fieldType = "string"; if (i < types.Count && !string.IsNullOrEmpty(types[i])) { string typeKey = types[i].ToLower().Trim(); if (typeMap.ContainsKey(typeKey)) { fieldType = typeMap[typeKey]; } } string comment = i < comments.Count ? comments[i] : ""; if (!string.IsNullOrEmpty(comment)) { sb.AppendLine($"\t{i + 1}:{fieldType} {header}, // {comment}"); } else { sb.AppendLine($"\t{i + 1}:{fieldType} {header},"); } } sb.AppendLine("}"); sb.AppendLine(); sb.AppendLine($"struct {structName}"); sb.AppendLine("{"); sb.AppendLine($"\t1:map {structName.ToLower()}s,"); sb.AppendLine("}"); return sb.ToString(); } /// /// 清理并转换字段名为PascalCase(与Thrift生成的C#属性匹配) /// 规则:只将第一个字母大写,其余保持不变 /// 示例: key -> Key, en_US -> En_US, merge_id_list -> Merge_id_list /// private string SanitizeFieldName(string fieldName) { if (string.IsNullOrWhiteSpace(fieldName)) return "Field_unknown"; // 移除空格和特殊字符,保留字母、数字和下划线 StringBuilder sb = new StringBuilder(); foreach (char c in fieldName) { if (char.IsLetterOrDigit(c) || c == '_') sb.Append(c); else if (c == ' ' || c == '-') sb.Append('_'); } string cleaned = sb.ToString(); if (string.IsNullOrEmpty(cleaned)) return "Field_unknown"; // 只将首字母大写,其余保持不变 if (cleaned.Length > 0) { return char.ToUpper(cleaned[0]) + cleaned.Substring(1); } return cleaned; } /// /// 生成 AllConfigs.thrift 内容 /// private string GenerateAllConfigsContent(List structNames) { StringBuilder sb = new StringBuilder(); sb.AppendLine("namespace netstd Byway.Thrift.Data"); sb.AppendLine(); // 添加所有 include foreach (string structName in structNames) { sb.AppendLine($"include \"{structName}.thrift\""); } sb.AppendLine(); sb.AppendLine("struct AllConfigs"); sb.AppendLine("{"); // 添加所有字段(PascalCase命名) for (int i = 0; i < structNames.Count; i++) { string structName = structNames[i]; sb.AppendLine($"\t{i + 1}:{structName}.{structName} {structName},"); } sb.AppendLine("}"); return sb.ToString(); } // ========== 步骤2: 编译 Thrift 到 C# ========== private async Task Step2_CompileToCSharp(Dictionary paths) { Log("\n【步骤2】编译 Thrift 到 C#"); Log("--------------------------------------------------------------------------------"); try { string compiler = paths["compiler"]; string thriftDir = paths["thrift_dir"]; string outputDir = paths["csharp_output_dir"]; // 先生成到Thrift项目的中间目录 // 清空输出目录 if (Directory.Exists(outputDir)) { Directory.Delete(outputDir, true); } Directory.CreateDirectory(outputDir); // 获取所有 thrift 文件 string[] thriftFiles = Directory.GetFiles(thriftDir, "*.thrift"); // 过滤:只编译 cfg_txt.json 中定义的配置 + AllConfigs var filesToCompile = thriftFiles.Where(f => { string fileName = Path.GetFileNameWithoutExtension(f); return fileName == "AllConfigs" || configStructNames.Contains(fileName); }).ToArray(); Log($" 找到 {thriftFiles.Length} 个 thrift 文件"); Log($" 将编译 {filesToCompile.Length} 个配置文件(过滤了 {thriftFiles.Length - filesToCompile.Length} 个无关文件)"); Log($" 输出目录: {outputDir}"); Log(""); int successCount = 0; List failedList = new List(); foreach (string thriftFile in filesToCompile) { string fileName = Path.GetFileName(thriftFile); Log($" 编译: {fileName}"); // 调用 thrift.exe(参数与 Python 版本完全一致) ProcessStartInfo psi = new ProcessStartInfo { FileName = compiler, Arguments = $"-strict -v -r --gen netstd:unity,serial,async_postfix -out \"{outputDir}\" \"{thriftFile}\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, WorkingDirectory = thriftDir }; using (Process process = Process.Start(psi)) { string output = await process.StandardOutput.ReadToEndAsync(); string error = await process.StandardError.ReadToEndAsync(); await Task.Run(() => process.WaitForExit()); if (process.ExitCode == 0) { Log($" [OK] 成功"); successCount++; } else { Log($" [FAIL] 失败"); if (!string.IsNullOrEmpty(error)) Log($" 错误: {error}"); failedList.Add(fileName); } } } // 检查生成的 C# 文件 string csharpActualDir = Path.Combine(outputDir, "Byway", "Thrift", "Data"); int csFilesCount = 0; if (Directory.Exists(csharpActualDir)) { csFilesCount = Directory.GetFiles(csharpActualDir, "*.cs").Length; Log($"\n 生成了 {csFilesCount} 个 C# 文件"); } Log($"\n编译完成: {successCount}/{filesToCompile.Length}"); AddStepReport("编译到C#", true, new Dictionary { { "success", successCount }, { "failed", failedList.Count }, { "total", filesToCompile.Length }, { "cs_files_count", csFilesCount } }); return successCount > 0; } catch (Exception ex) { Log($"[FAIL] 步骤2失败: {ex.Message}"); Log(ex.StackTrace); AddStepReport("编译到C#", false, new Dictionary { { "error", ex.Message } }); return false; } } // ========== 步骤3: 生成 Bytes 文件 ========== private async Task Step3_GenerateBytes(Dictionary paths) { Log("\n【步骤3】生成 Bytes 文件"); Log("--------------------------------------------------------------------------------"); try { string cfgJsonPath = paths["cfg_json"]; string configDir = paths["config_dir"]; string bytesOutputDir = paths["bytes_output_dir"]; string csharpOutputDir = paths["csharp_output_dir"]; string unityCSharpDir = paths["unity_csharp_dir"]; // 创建输出目录 if (!Directory.Exists(bytesOutputDir)) Directory.CreateDirectory(bytesOutputDir); // 临时复制C#文件到Unity以便编译和加载 Log(" [1/4] 临时复制C#文件到Unity"); string sourceDir = Path.Combine(csharpOutputDir, "Byway", "Thrift", "Data"); string targetDir = Path.Combine(unityCSharpDir, "Byway", "Thrift", "Data"); if (!Directory.Exists(sourceDir)) { Log($"[FAIL] 中间C#目录不存在: {sourceDir}"); return false; } if (!Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir); // 复制所有.cs文件(除了Extensions) string[] csFiles = Directory.GetFiles(sourceDir, "*.cs", SearchOption.TopDirectoryOnly); int copiedCount = 0; foreach (string csFile in csFiles) { string fileName = Path.GetFileName(csFile); if (fileName.Contains("Extensions.cs")) { Log($" [SKIP] 跳过: {fileName}"); continue; } string destFile = Path.Combine(targetDir, fileName); File.Copy(csFile, destFile, true); copiedCount++; } Log($" [OK] 已复制 {copiedCount} 个C#文件"); // 等待Unity编译 Log(" [2/4] 等待Unity编译"); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); await Task.Delay(2000); // 加载程序集 Log(" [3/4] 加载程序集并生成bytes"); System.Reflection.Assembly assembly = null; foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { if (asm.GetType("Byway.Thrift.Data.AllConfigs") != null) { assembly = asm; break; } } if (assembly == null) { Log("[FAIL] 找不到程序集"); return false; } // 读取配置 string cfgText = File.ReadAllText(cfgJsonPath); var cfg = JsonConvert.DeserializeObject(cfgText); var fileList = cfg["file_list"] as JArray; // 创建AllConfigs实例 var allConfigsType = assembly.GetType("Byway.Thrift.Data.AllConfigs"); object allConfigsInstance = Activator.CreateInstance(allConfigsType); int successCount = 0; // 填充每个配置 int processedCount = 0; foreach (JObject configItem in fileList) { string inFile = configItem["in_file"]?.ToString(); string outFile = configItem["out_file"]?.ToString(); string sheetName = configItem["sheet_name"]?.ToString(); var columnTypes = configItem["coloum_type"] as JArray; if (string.IsNullOrEmpty(inFile) || string.IsNullOrEmpty(outFile)) continue; string structName = outFile.Replace(".txt", ""); string excelPath = Path.Combine(configDir, inFile); if (!File.Exists(excelPath)) { Log($" [{++processedCount}] [SKIP] {structName} - Excel文件不存在"); continue; } Log($" [{++processedCount}] 处理: {structName}"); if (FillThriftObject(allConfigsInstance, allConfigsType, structName, excelPath, sheetName, columnTypes, assembly)) { successCount++; } } // 序列化为bytes(关键:使用ToArray而不是GetBuffer) Log($"\n 序列化AllConfigs.bytes (已设置 {successCount} 个配置)"); // 检查有多少配置实际被设置 int setCount = 0; var issetField = allConfigsType.GetField("__isset", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); if (issetField != null) { var issetObj = issetField.GetValue(allConfigsInstance); var issetType = issetObj.GetType(); foreach (var prop in allConfigsType.GetProperties()) { if (prop.Name != "Isset" && prop.PropertyType.Namespace == "Byway.Thrift.Data") { var issetProp = issetType.GetProperty(prop.Name); if (issetProp != null && (bool)issetProp.GetValue(issetObj)) setCount++; } } } Log($" __isset 标志统计: {setCount} 个配置被标记为已设置"); using (var memoryStream = new MemoryStream()) { using (var transport = new TStreamTransport(memoryStream, memoryStream, new Thrift.TConfiguration())) { using (var protocol = new TCompactProtocol(transport)) { var writeMethod = allConfigsType.GetMethod("WriteAsync"); if (writeMethod != null) { var task = (Task)writeMethod.Invoke(allConfigsInstance, new object[] { protocol, CancellationToken.None }); await task; // 关键:使用ToArray()获取实际写入的数据(对应Python的getvalue()) byte[] bytes = memoryStream.ToArray(); string allConfigsBytesPath = Path.Combine(bytesOutputDir, "AllConfigs.bytes"); File.WriteAllBytes(allConfigsBytesPath, bytes); Log($" [OK] 生成成功 ({bytes.Length} bytes / {bytes.Length / 1024.0:F2} KB)"); if (bytes.Length < 1000) { Log($" [WARNING] 文件太小 ({bytes.Length} bytes),可能大部分配置未序列化!"); } } } } } // 清理Unity中的临时文件 Log(" [4/4] 清理临时C#文件"); if (Directory.Exists(targetDir)) { try { Directory.Delete(targetDir, true); // 清理空目录 string thriftFolder = Path.GetDirectoryName(targetDir); if (Directory.Exists(thriftFolder) && Directory.GetFileSystemEntries(thriftFolder).Length == 0) Directory.Delete(thriftFolder); string bywayFolder = Path.GetDirectoryName(thriftFolder); if (Directory.Exists(bywayFolder) && Directory.GetFileSystemEntries(bywayFolder).Length == 0) Directory.Delete(bywayFolder); AssetDatabase.Refresh(); Log($" [OK] 已清理临时文件"); } catch (Exception ex) { Log($" [WARN] 清理失败: {ex.Message}"); } } Log($"\n生成完成: 成功 {successCount} 个配置"); AddStepReport("生成Bytes文件", true, new Dictionary { { "success", successCount } }); return true; } catch (Exception ex) { Log($"[FAIL] 步骤3失败: {ex.Message}"); Log(ex.StackTrace); AddStepReport("生成Bytes文件", false, new Dictionary { { "error", ex.Message } }); return false; } } /// /// 填充Thrift对象数据 /// private bool FillThriftObject(object allConfigsInstance, Type allConfigsType, string structName, string excelPath, string sheetName, JArray columnTypes, System.Reflection.Assembly assembly) { try { var configType = assembly.GetType($"Byway.Thrift.Data.{structName}"); var itemType = assembly.GetType($"Byway.Thrift.Data.{structName}Item"); if (configType == null || itemType == null) return false; object configInstance = Activator.CreateInstance(configType); var dictType = typeof(Dictionary<,>).MakeGenericType(typeof(int), itemType); var dictInstance = Activator.CreateInstance(dictType); // 读取Excel FileInfo fileInfo = new FileInfo(excelPath); using (ExcelPackage package = new ExcelPackage(fileInfo)) { ExcelWorksheet worksheet = null; if (!string.IsNullOrEmpty(sheetName) && package.Workbook.Worksheets[sheetName] != null) worksheet = package.Workbook.Worksheets[sheetName]; else worksheet = package.Workbook.Worksheets[0]; if (worksheet == null || worksheet.Dimension == null) return false; int maxRows = worksheet.Dimension.End.Row; int maxCols = worksheet.Dimension.End.Column; // 读取表头(第1行) List headers = new List(); List validColumns = new List(); int maxColumns = columnTypes?.Count ?? maxCols; for (int col = 1; col <= maxCols && headers.Count < maxColumns; col++) { string header = worksheet.Cells[1, col].Value?.ToString() ?? ""; if (!string.IsNullOrWhiteSpace(header)) { headers.Add(header); validColumns.Add(col); } } // 读取数据(从第3行开始) int filledItemCount = 0; for (int row = 3; row <= maxRows; row++) { bool isEmptyRow = true; for (int i = 0; i < validColumns.Count; i++) { if (worksheet.Cells[row, validColumns[i]].Value != null) { isEmptyRow = false; break; } } if (isEmptyRow) continue; object itemInstance = Activator.CreateInstance(itemType); int? idValue = null; int fieldSetCount = 0; for (int i = 0; i < headers.Count; i++) { string originalHeader = headers[i]; string fieldName = SanitizeFieldName(originalHeader); int colIndex = validColumns[i]; var cell = worksheet.Cells[row, colIndex]; object cellValue = cell.Value; var property = itemType.GetProperty(fieldName); if (property != null) { try { object convertedValue; // 处理null值 if (cellValue == null) { if (property.PropertyType == typeof(string)) { convertedValue = ""; } else if (property.PropertyType.IsValueType) { // 值类型(int, bool, double等)设置默认值(0, false, 0.0等) convertedValue = Activator.CreateInstance(property.PropertyType); } else { // 引用类型跳过 continue; } } else { convertedValue = ConvertCellValue(cellValue, property.PropertyType); } property.SetValue(itemInstance, convertedValue); fieldSetCount++; if (fieldName.ToLower() == "id" && convertedValue is int) idValue = (int)convertedValue; } catch (Exception ex) { Log($" [WARN] 行{row} 字段{fieldName}转换失败: {ex.Message}"); } } else { // 尝试查找所有属性来调试 if (filledItemCount == 0) // 只在第一项时输出 { Log($" [DEBUG] 未找到属性: {fieldName} (原始: {originalHeader})"); Log($" [DEBUG] {itemType.Name} 可用属性: {string.Join(", ", itemType.GetProperties().Select(p => p.Name))}"); } } } if (filledItemCount == 0) { if (fieldSetCount < headers.Count) { // 找出未设置的字段 var unsetFields = new List(); for (int i = 0; i < headers.Count; i++) { var h = headers[i]; var fieldName = SanitizeFieldName(h); var property = itemType.GetProperty(fieldName); if (property == null) { unsetFields.Add(h); continue; } var colIndex = validColumns[i]; var cellValue = worksheet.Cells[row, colIndex].Value; // 如果是null且不是string类型,则算作未设置 if (cellValue == null && property.PropertyType != typeof(string)) { unsetFields.Add(h); } } Log($" 第一行数据: ID={idValue}, 设置了 {fieldSetCount}/{headers.Count} 个字段" + (unsetFields.Any() ? $" (未设置: {string.Join(", ", unsetFields)})" : "")); } else { Log($" 第一行数据: ID={idValue}, 设置了 {fieldSetCount}/{headers.Count} 个字段"); } } if (idValue.HasValue) { var addMethod = dictType.GetMethod("Add"); addMethod.Invoke(dictInstance, new object[] { idValue.Value, itemInstance }); filledItemCount++; } } } // 获取字典中的条目数量 var countProperty = dictType.GetProperty("Count"); int itemCount = countProperty != null ? (int)countProperty.GetValue(dictInstance) : 0; // 设置字典属性(格式:首字母大写 + 其余小写 + s,如 Adgiftdatas) string dictPropertyName = char.ToUpper(structName[0]) + structName.Substring(1).ToLower() + "s"; var dictProperty = configType.GetProperty(dictPropertyName); if (dictProperty != null) { dictProperty.SetValue(configInstance, dictInstance); Log($" 字典属性: {dictPropertyName} = {itemCount} 条"); } else { Log($" [ERROR] 未找到字典属性: {dictPropertyName} (类: {configType.Name})"); return false; } // 设置到AllConfigs var allConfigProperty = allConfigsType.GetProperty(structName); if (allConfigProperty != null) { allConfigProperty.SetValue(allConfigsInstance, configInstance); // 验证设置成功 var verifyValue = allConfigProperty.GetValue(allConfigsInstance); if (verifyValue == null) { Log($" [ERROR] 设置到AllConfigs后为null!"); return false; } // 验证__isset标志 var issetField = allConfigsType.GetField("__isset", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); if (issetField != null) { var issetObj = issetField.GetValue(allConfigsInstance); var issetType = issetObj.GetType(); var issetProperty = issetType.GetProperty(structName); if (issetProperty != null) { bool issetValue = (bool)issetProperty.GetValue(issetObj); Log($" AllConfigs.{structName} = OK (__isset={issetValue})"); if (!issetValue) { Log($" [ERROR] __isset.{structName} = false!"); return false; } } } } else { Log($" [ERROR] AllConfigs中未找到属性: {structName}"); return false; } return true; } catch (Exception ex) { Log($" [ERROR] {structName}: {ex.Message}"); return false; } } /// /// 转换单元格值 /// private object ConvertCellValue(object cellValue, Type targetType) { try { if (targetType == typeof(int)) { if (cellValue is double d) return (int)d; return Convert.ToInt32(cellValue); } else if (targetType == typeof(long)) { if (cellValue is double d) return (long)d; return Convert.ToInt64(cellValue); } else if (targetType == typeof(double)) { return Convert.ToDouble(cellValue); } else if (targetType == typeof(bool)) { return Convert.ToBoolean(cellValue); } else if (targetType == typeof(string)) { return cellValue.ToString(); } return cellValue; } catch { if (targetType == typeof(int)) return 0; if (targetType == typeof(long)) return 0L; if (targetType == typeof(double)) return 0.0; if (targetType == typeof(bool)) return false; if (targetType == typeof(string)) return ""; return null; } } // ========== 步骤4: 清理冲突文件 ========== private bool Step4_CleanupExtensions(Dictionary paths) { Log("\n【步骤4】清理冲突的 Extensions 文件"); Log("--------------------------------------------------------------------------------"); try { string csharpOutputDir = paths["csharp_output_dir"]; string actualDir = Path.Combine(csharpOutputDir, "Byway", "Thrift", "Data"); if (!Directory.Exists(actualDir)) { Log(" [WARN] C# 目录不存在"); return true; } // 查找 AllConfigs.Extensions.cs string[] extensionFiles = Directory.GetFiles(actualDir, "*AllConfigs.Extensions.cs", SearchOption.TopDirectoryOnly); int deletedCount = 0; foreach (string file in extensionFiles) { string fileName = Path.GetFileName(file); Log($" 删除: {fileName}"); try { File.Delete(file); deletedCount++; Log($" [OK] 已删除"); } catch (Exception ex) { Log($" [WARN] 删除失败: {ex.Message}"); } } if (deletedCount == 0) { Log(" 未发现需要删除的 Extensions 文件"); } Log($"\n清理完成: 删除 {deletedCount} 个文件"); AddStepReport("清理冲突文件", true, new Dictionary { { "deleted", deletedCount } }); return true; } catch (Exception ex) { Log($"[FAIL] 步骤4失败: {ex.Message}"); AddStepReport("清理冲突文件", false, new Dictionary { { "error", ex.Message } }); return false; } } // ========== 步骤5: 复制 C# 到 Unity ========== private bool Step5_CopyCSharp(Dictionary paths) { Log("\n【步骤5】复制 C# 文件到 Unity"); Log("--------------------------------------------------------------------------------"); try { string csharpOutputDir = paths["csharp_output_dir"]; string unityCSharpDir = paths["unity_csharp_dir"]; string sourceDir = Path.Combine(csharpOutputDir, "Byway", "Thrift", "Data"); string targetDir = Path.Combine(unityCSharpDir, "Byway", "Thrift", "Data"); if (!Directory.Exists(sourceDir)) { Log(" [FAIL] 源目录不存在"); return false; } // 创建目标目录 if (Directory.Exists(targetDir)) { Directory.Delete(targetDir, true); } Directory.CreateDirectory(targetDir); // 复制所有C#文件 int copiedCount = 0; foreach (string file in Directory.GetFiles(sourceDir, "*.cs")) { string fileName = Path.GetFileName(file); string destFile = Path.Combine(targetDir, fileName); File.Copy(file, destFile, true); copiedCount++; } Log($"\n复制完成: {copiedCount} 个 C# 文件"); AddStepReport("复制C#到Unity", true, new Dictionary { { "count", copiedCount } }); return true; } catch (Exception ex) { Log($"[FAIL] 步骤5失败: {ex.Message}"); Log(ex.StackTrace); AddStepReport("复制C#到Unity", false, new Dictionary { { "error", ex.Message } }); return false; } } // ========== 步骤6: 复制 Bytes 到 Unity ========== private bool Step6_CopyBytes(Dictionary paths) { Log("\n【步骤6】复制 Bytes 文件到 Unity"); Log("--------------------------------------------------------------------------------"); try { string bytesOutputDir = paths["bytes_output_dir"]; string unityBytesDir = paths["unity_bytes_dir"]; if (!Directory.Exists(unityBytesDir)) { Directory.CreateDirectory(unityBytesDir); } // 只复制 AllConfigs.bytes string sourceFile = Path.Combine(bytesOutputDir, "AllConfigs.bytes"); string targetFile = Path.Combine(unityBytesDir, "AllConfigs.bytes"); if (File.Exists(sourceFile)) { File.Copy(sourceFile, targetFile, true); Log(" [OK] 复制 AllConfigs.bytes"); AddStepReport("复制Bytes到Unity", true, new Dictionary { { "count", 1 } }); return true; } else { Log(" [FAIL] 未找到 AllConfigs.bytes"); return false; } } catch (Exception ex) { Log($"[FAIL] 步骤6失败: {ex.Message}"); AddStepReport("复制Bytes到Unity", false, new Dictionary { { "error", ex.Message } }); return false; } } // ========== 步骤7: 生成 DR 文件 ========== private bool Step7_GenerateDR(Dictionary paths) { Log("\n【步骤5】生成 DR 文件"); Log("--------------------------------------------------------------------------------"); try { // 从Unity项目中读取C#文件(已经复制过来的) string unityCSharpDir = paths["unity_csharp_dir"]; string drOutputDir = paths["dr_output_dir"]; string actualDir = Path.Combine(unityCSharpDir, "Byway", "Thrift", "Data"); // 创建 DR 输出目录 if (!Directory.Exists(drOutputDir)) Directory.CreateDirectory(drOutputDir); // 查找所有配置类(不包括 AllConfigs 和 Item 类) string[] csFiles = Directory.GetFiles(actualDir, "*.cs"); int generatedCount = 0; foreach (string csFile in csFiles) { string fileName = Path.GetFileName(csFile); // 跳过 ItemItem(数据类)、AllConfigs、Extensions 文件 // 注意:PetAirItem.cs 是配置类(应该生成DR),PetAirItemItem.cs 是数据类(不生成DR) if (fileName.EndsWith("ItemItem.cs") || fileName.Contains("AllConfigs") || fileName.Contains("Extensions")) continue; // 解析配置类 var configInfo = ParseConfigClass(csFile); if (configInfo == null) continue; string className = configInfo["class_name"]; Log($" 生成: DR{className}"); // 查找对应的 Item 类 string itemFileName = configInfo["item_type"] + ".cs"; string itemFilePath = Path.Combine(actualDir, itemFileName); if (!File.Exists(itemFilePath)) { Log($" [WARN] 找不到 Item 文件: {itemFileName}"); continue; } // 解析 Item 类属性 var properties = ParseItemClass(itemFilePath); // 生成 DR 代码 string drCode = GenerateDRCode(configInfo, properties); // 写入文件 string drFilePath = Path.Combine(drOutputDir, $"DR{configInfo["class_name"]}.cs"); File.WriteAllText(drFilePath, drCode); Log($" [OK] 生成完成"); generatedCount++; } Log($"\n生成完成: {generatedCount} 个 DR 文件"); AddStepReport("生成DR文件", true, new Dictionary { { "total", generatedCount } }); return true; } catch (Exception ex) { Log($"[FAIL] 步骤5失败: {ex.Message}"); Log(ex.StackTrace); AddStepReport("生成DR文件", false, new Dictionary { { "error", ex.Message } }); return false; } } /// /// 解析配置类文件 /// private Dictionary ParseConfigClass(string filePath) { try { string content = File.ReadAllText(filePath); // 提取类名 var classMatch = Regex.Match(content, @"public partial class (\w+) : TBase"); if (!classMatch.Success) return null; string className = classMatch.Groups[1].Value; // 提取 Dictionary 属性(支持 global:: 命名空间) // 注意:必须匹配公开属性(public Dictionary),而不是私有字段(_xxx) // 使用多行匹配,确保找到完整的属性定义 var propPattern = @"public\s+Dictionary\s+(\w+)\s*\{"; var propMatch = Regex.Match(content, propPattern, RegexOptions.Multiline); if (!propMatch.Success) { // 如果没找到,记录详细信息用于调试 return null; } string itemType = propMatch.Groups[1].Value; string dictProperty = propMatch.Groups[2].Value; return new Dictionary { { "class_name", className }, { "item_type", itemType }, { "dict_property", dictProperty } }; } catch { return null; } } /// /// 解析 Item 类 /// private List> ParseItemClass(string filePath) { List> properties = new List>(); try { string content = File.ReadAllText(filePath); // 匹配所有公共属性 var matches = Regex.Matches(content, @"public\s+(\w+(?:<[^>]+>)?)\s+(\w+)\s*\{"); foreach (Match match in matches) { string type = match.Groups[1].Value; string name = match.Groups[2].Value; // 跳过 __isset 和 Isset if (name == "__isset" || name == "Isset") continue; var (finalType, defaultValue) = GetTypeInfo(type); properties.Add(new Dictionary { { "type", finalType }, { "name", name }, { "original_type", type }, // 保存原始类型用于判断 { "default", defaultValue } }); } } catch { // 忽略错误 } return properties; } /// /// 生成 DR 类代码 /// private string GenerateDRCode(Dictionary configInfo, List> properties) { string className = configInfo["class_name"]; string itemType = configInfo["item_type"]; string dictProperty = configInfo["dict_property"]; StringBuilder sb = new StringBuilder(); sb.AppendLine("// 此文件由 ThriftIntegratedPipeline 自动生成,请勿手动修改"); sb.AppendLine($"// 配置类: {className}"); sb.AppendLine($"// 数据类: {itemType}"); sb.AppendLine(); sb.AppendLine("using UnityEngine;"); sb.AppendLine("using Byway.Config;"); sb.AppendLine("using Byway.Thrift.Data;"); sb.AppendLine("using UnityGameFramework.Runtime;"); sb.AppendLine(); sb.AppendLine("namespace CrazyMaple"); sb.AppendLine("{"); sb.AppendLine(" /// "); sb.AppendLine($" /// {className} 数据行"); sb.AppendLine(" /// "); sb.AppendLine($" public class DR{className} : DataRowBase"); sb.AppendLine(" {"); sb.AppendLine($" private {itemType} _configData;"); sb.AppendLine(); // Id 属性 var idProp = properties.FirstOrDefault(p => p["name"].ToLower() == "id"); sb.AppendLine(" /// "); sb.AppendLine(" /// 唯一标识"); sb.AppendLine(" /// "); sb.AppendLine(" public override int Id"); sb.AppendLine(" {"); sb.AppendLine(" get"); sb.AppendLine(" {"); if (idProp != null) { sb.AppendLine($" return _configData?.{idProp["name"]} ?? 0;"); } else { sb.AppendLine(" return 0;"); } sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine(); // 其他属性 foreach (var prop in properties) { string propName = prop["name"]; // 跳过ID字段(已经作为override属性生成) if (propName.ToLower() == "id") continue; string propType = prop["type"]; string originalType = prop.ContainsKey("original_type") ? prop["original_type"] : propType; string defaultValue = prop.ContainsKey("default") ? prop["default"] : ""; if (string.IsNullOrEmpty(defaultValue)) { var (_, defVal) = GetTypeInfo(propType); defaultValue = defVal; } sb.AppendLine(" /// "); sb.AppendLine($" /// {propName}"); sb.AppendLine(" /// "); // 特殊处理:double类型在DR中返回float(与Python版本一致) if (originalType.ToLower().Contains("double")) { sb.AppendLine($" public float {propName}"); sb.AppendLine(" {"); sb.AppendLine(" get"); sb.AppendLine(" {"); sb.AppendLine($" return System.Convert.ToSingle(_configData?.{propName} ?? 0.0);"); sb.AppendLine(" }"); sb.AppendLine(" }"); } else { sb.AppendLine($" public {propType} {propName}"); sb.AppendLine(" {"); sb.AppendLine(" get"); sb.AppendLine(" {"); sb.AppendLine($" return _configData?.{propName} ?? {defaultValue};"); sb.AppendLine(" }"); sb.AppendLine(" }"); } sb.AppendLine(); } // UIForm 特殊处理:使用完整命名空间类型名 string fullClassName = className == "UIForm" ? "Byway.Thrift.Data.UIForm" : className; // LoadFromConfig 方法(优化:支持可选的配置实例参数和直接传入 Item) sb.AppendLine(" /// "); sb.AppendLine(" /// 从配置加载数据(优先使用传入的配置实例)"); sb.AppendLine(" /// "); sb.AppendLine($" public void LoadFromConfig(int id, {fullClassName} config = null)"); sb.AppendLine(" {"); sb.AppendLine(" if (config == null)"); sb.AppendLine(" {"); sb.AppendLine($" config = ConfigManager.Instance.GetConfig<{fullClassName}>();"); sb.AppendLine(" }"); sb.AppendLine(" "); sb.AppendLine($" if (config?.{dictProperty} != null)"); sb.AppendLine(" {"); sb.AppendLine($" config.{dictProperty}.TryGetValue(id, out _configData);"); sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine(); // 添加直接设置 Item 的方法(性能优化) sb.AppendLine(" /// "); sb.AppendLine(" /// 直接设置配置数据(性能优化:跳过字典查询)"); sb.AppendLine(" /// "); sb.AppendLine($" public void SetConfigData({itemType} configData)"); sb.AppendLine(" {"); sb.AppendLine(" _configData = configData;"); sb.AppendLine(" }"); sb.AppendLine(); // ParseDataRow 方法(优化:使用 userData 传入的配置实例,或直接传入 Item) sb.AppendLine(" /// "); sb.AppendLine(" /// 解析数据行(优化:使用 userData 传入的配置实例,避免重复调用 GetConfig)"); sb.AppendLine(" /// "); sb.AppendLine(" public override bool ParseDataRow(string dataRowString, object userData)"); sb.AppendLine(" {"); sb.AppendLine(" int id = 0;"); sb.AppendLine(" if (!int.TryParse(dataRowString, out id))"); sb.AppendLine(" {"); sb.AppendLine(" return false;"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" // 性能优化:尝试从 userData 获取配置字典,直接获取 Item"); sb.AppendLine(" if (userData is System.Collections.Generic.Dictionary userDataDict)"); sb.AppendLine(" {"); sb.AppendLine(" // 优先尝试从缓存的字典直接获取 Item(最快)"); sb.AppendLine(" if (userDataDict.TryGetValue(\"ConfigDict\", out object dictObj))"); sb.AppendLine(" {"); sb.AppendLine(" var dict = dictObj as System.Collections.Generic.Dictionary;"); sb.AppendLine(" if (dict != null && dict.TryGetValue(id, out var item))"); sb.AppendLine(" {"); sb.AppendLine(" _configData = item;"); sb.AppendLine(" return true;"); sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" // 备选方案:从配置实例获取"); sb.AppendLine(" if (userDataDict.TryGetValue(\"ConfigInstance\", out object configObj))"); sb.AppendLine(" {"); sb.AppendLine($" var config = configObj as {fullClassName};"); sb.AppendLine(" if (config != null)"); sb.AppendLine(" {"); sb.AppendLine(" LoadFromConfig(id, config);"); sb.AppendLine(" return _configData != null;"); sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" // 兜底方案:直接查询(最慢)"); sb.AppendLine(" LoadFromConfig(id);"); sb.AppendLine(" return _configData != null;"); sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine("}"); return sb.ToString(); } /// /// 获取类型信息和默认值 /// private (string type, string defaultValue) GetTypeInfo(string csType) { if (csType.Contains("Dictionary") || csType.Contains("List")) { return (csType, "null"); } switch (csType.ToLower()) { case "int": case "i32": return ("int", "0"); case "long": case "i64": return ("long", "0"); case "double": case "float": return ("double", "0.0"); case "bool": case "boolean": return ("bool", "false"); case "string": return ("string", "\"\""); default: return ("string", "\"\""); } } // ========== 报告生成 ========== private string GenerateReport() { StringBuilder sb = new StringBuilder(); sb.AppendLine("================================================================================"); sb.AppendLine(" Thrift 流程执行报告"); sb.AppendLine("================================================================================"); sb.AppendLine($"执行时间: {startTime:yyyy-MM-dd HH:mm:ss} -> {endTime:yyyy-MM-dd HH:mm:ss}"); sb.AppendLine($"总耗时: {(endTime - startTime).TotalSeconds:F2} 秒"); sb.AppendLine(); // 步骤摘要 int totalSteps = reportSteps.Count; int successSteps = reportSteps.Count(s => s.Success); sb.AppendLine("步骤摘要:"); for (int i = 0; i < reportSteps.Count; i++) { var step = reportSteps[i]; string status = step.Success ? "[OK]" : "[FAIL]"; sb.AppendLine($" {i + 1}. {status} {step.Name}"); } sb.AppendLine(); if (successSteps == totalSteps) { sb.AppendLine("【总体状态】[OK] 所有步骤成功完成,无错误"); } else { sb.AppendLine($"【总体状态】[WARNING] {totalSteps - successSteps} 个步骤失败"); } sb.AppendLine(); sb.AppendLine("================================================================================"); sb.AppendLine(" 详细执行报告"); sb.AppendLine("================================================================================"); sb.AppendLine(); // 详细步骤信息 for (int i = 0; i < reportSteps.Count; i++) { var step = reportSteps[i]; string status = step.Success ? "[OK]" : "[FAIL]"; sb.AppendLine($"【步骤 {i + 1}】{step.Name}"); sb.AppendLine($"状态: {status}"); sb.AppendLine($"时间: {step.Time:HH:mm:ss}"); sb.AppendLine("--------------------------------------------------------------------------------"); if (step.Details != null && step.Details.Count > 0) { foreach (var detail in step.Details) { sb.AppendLine($" {detail.Key}: {detail.Value}"); } } sb.AppendLine(); } // 配置路径信息 sb.AppendLine("================================================================================"); sb.AppendLine(" 配置路径信息"); sb.AppendLine("================================================================================"); var paths = GetDerivedPaths(); sb.AppendLine("[基础路径]"); sb.AppendLine($" - Docs 项目: {docsProjectPath}"); sb.AppendLine($" - Thrift 项目: {thriftProjectPath}"); sb.AppendLine(); sb.AppendLine("[派生路径]"); sb.AppendLine($" - cfg_txt.json: {paths["cfg_json"]}"); sb.AppendLine($" - Config 目录: {paths["config_dir"]}"); sb.AppendLine($" - Thrift 编译器: {paths["compiler"]}"); sb.AppendLine($" - Unity C# 目录: {paths["unity_csharp_dir"]}"); sb.AppendLine($" - Unity Bytes 目录: {paths["unity_bytes_dir"]}"); sb.AppendLine($" - DR 生成目录: {paths["dr_output_dir"]}"); sb.AppendLine("================================================================================"); return sb.ToString(); } private class StepReport { public string Name { get; set; } public bool Success { get; set; } public DateTime Time { get; set; } public Dictionary Details { get; set; } } } }