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; }
}
}
}