2068 lines
87 KiB
C#
2068 lines
87 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 导入 Docs 配置表工具
|
||
/// 完全复刻 integrated_pipeline.py 的功能
|
||
/// 从 Excel 到 Unity 的一站式处理
|
||
/// </summary>
|
||
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<StepReport> reportSteps = new List<StepReport>();
|
||
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<string> configStructNames = new List<string>();
|
||
|
||
[MenuItem("蹊径/Thrift/导入 Docs 配置表工具")]
|
||
public static void ShowWindow()
|
||
{
|
||
var window = GetWindow<ThriftIntegratedPipelineEditor>("导入 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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绘制路径配置区域
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绘制执行区域
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绘制日志区域
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载配置(使用 EditorPrefs,本地保存,不会被git提交)
|
||
/// </summary>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存配置(使用 EditorPrefs,本地保存,不会被git提交)
|
||
/// </summary>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有派生路径
|
||
/// </summary>
|
||
private Dictionary<string, string> GetDerivedPaths()
|
||
{
|
||
var paths = new Dictionary<string, string>();
|
||
|
||
// 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证路径
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 日志输出
|
||
/// </summary>
|
||
private void Log(string message)
|
||
{
|
||
logBuilder.AppendLine(message);
|
||
Repaint();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加步骤报告
|
||
/// </summary>
|
||
private void AddStepReport(string stepName, bool success, Dictionary<string, object> details = null)
|
||
{
|
||
reportSteps.Add(new StepReport
|
||
{
|
||
Name = stepName,
|
||
Success = success,
|
||
Time = DateTime.Now,
|
||
Details = details ?? new Dictionary<string, object>()
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新进度
|
||
/// </summary>
|
||
private void UpdateProgress(int step, string stepName)
|
||
{
|
||
completedSteps = step;
|
||
currentStep = stepName;
|
||
progress = (float)completedSteps / totalSteps;
|
||
Repaint();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行完整流程
|
||
/// </summary>
|
||
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失败,终止执行");
|
||
return;
|
||
}
|
||
|
||
// 步骤2: 编译 Thrift 到 C#
|
||
UpdateProgress(1, "步骤2: 编译 Thrift 到 C#");
|
||
if (!await Step2_CompileToCSharp(paths))
|
||
{
|
||
Log("[FAIL] 步骤2失败,终止执行");
|
||
return;
|
||
}
|
||
|
||
// 步骤3: 生成 Bytes 文件
|
||
UpdateProgress(2, "步骤3: 生成 Bytes 文件");
|
||
if (!await Step3_GenerateBytes(paths))
|
||
{
|
||
Log("[FAIL] 步骤3失败,终止执行");
|
||
return;
|
||
}
|
||
|
||
// 步骤4: 清理冲突文件
|
||
UpdateProgress(3, "步骤4: 清理冲突文件");
|
||
if (!Step4_CleanupExtensions(paths))
|
||
{
|
||
Log("[FAIL] 步骤4失败,终止执行");
|
||
return;
|
||
}
|
||
|
||
// 步骤5: 复制 C# 到 Unity
|
||
UpdateProgress(4, "步骤5: 复制 C# 到 Unity");
|
||
if (!Step5_CopyCSharp(paths))
|
||
{
|
||
Log("[FAIL] 步骤5失败,终止执行");
|
||
return;
|
||
}
|
||
|
||
// 步骤6: 复制 Bytes 到 Unity
|
||
UpdateProgress(5, "步骤6: 复制 Bytes 到 Unity");
|
||
if (!Step6_CopyBytes(paths))
|
||
{
|
||
Log("[FAIL] 步骤6失败,终止执行");
|
||
return;
|
||
}
|
||
|
||
// 步骤7: 生成 DR 文件
|
||
UpdateProgress(6, "步骤7: 生成 DR 文件");
|
||
if (!Step7_GenerateDR(paths))
|
||
{
|
||
Log("[FAIL] 步骤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);
|
||
EditorUtility.DisplayDialog("错误", $"执行失败:\n{ex.Message}", "确定");
|
||
}
|
||
finally
|
||
{
|
||
isExecuting = false;
|
||
AssetDatabase.Refresh();
|
||
Repaint();
|
||
}
|
||
}
|
||
|
||
// ========== 步骤1: 生成 Thrift 文件 ==========
|
||
|
||
private async Task<bool> Step1_GenerateThriftFiles(Dictionary<string, string> 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<JObject>(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<string> structNames = new List<string>();
|
||
|
||
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<string, object>
|
||
{
|
||
{ "total", generatedCount },
|
||
{ "files", structNames }
|
||
});
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[FAIL] 步骤1失败: {ex.Message}");
|
||
Log(ex.StackTrace);
|
||
AddStepReport("生成Thrift文件", false, new Dictionary<string, object>
|
||
{
|
||
{ "error", ex.Message }
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取 Excel 表头、类型和注释
|
||
/// 完全复刻 Python 的读取逻辑:跳过空表头列,而不是停止
|
||
/// </summary>
|
||
private (List<string> headers, List<string> types, List<string> 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<string> headers = new List<string>();
|
||
List<string> types = new List<string>();
|
||
List<string> comments = new List<string>();
|
||
|
||
// 只读取 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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成 Thrift 文件内容
|
||
/// </summary>
|
||
private string GenerateThriftContent(string structName, List<string> headers, List<string> types, List<string> comments)
|
||
{
|
||
StringBuilder sb = new StringBuilder();
|
||
|
||
sb.AppendLine("namespace netstd Byway.Thrift.Data");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"struct {structName}Item");
|
||
sb.AppendLine("{");
|
||
|
||
// 类型映射
|
||
Dictionary<string, string> typeMap = new Dictionary<string, string>
|
||
{
|
||
{ "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<i32,{structName}Item> {structName.ToLower()}s,");
|
||
sb.AppendLine("}");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理并转换字段名为PascalCase(与Thrift生成的C#属性匹配)
|
||
/// 规则:只将第一个字母大写,其余保持不变
|
||
/// 示例: key -> Key, en_US -> En_US, merge_id_list -> Merge_id_list
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成 AllConfigs.thrift 内容
|
||
/// </summary>
|
||
private string GenerateAllConfigsContent(List<string> 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<bool> Step2_CompileToCSharp(Dictionary<string, string> 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<string> failedList = new List<string>();
|
||
|
||
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<string, object>
|
||
{
|
||
{ "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<string, object>
|
||
{
|
||
{ "error", ex.Message }
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// ========== 步骤3: 生成 Bytes 文件 ==========
|
||
|
||
private async Task<bool> Step3_GenerateBytes(Dictionary<string, string> 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<JObject>(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 TBinaryProtocol(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<string, object>
|
||
{
|
||
{ "success", successCount }
|
||
});
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[FAIL] 步骤3失败: {ex.Message}");
|
||
Log(ex.StackTrace);
|
||
AddStepReport("生成Bytes文件", false, new Dictionary<string, object>
|
||
{
|
||
{ "error", ex.Message }
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 填充Thrift对象数据
|
||
/// </summary>
|
||
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<string> headers = new List<string>();
|
||
List<int> validColumns = new List<int>();
|
||
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<string>();
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 转换单元格值
|
||
/// </summary>
|
||
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<string, string> 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<string, object>
|
||
{
|
||
{ "deleted", deletedCount }
|
||
});
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[FAIL] 步骤4失败: {ex.Message}");
|
||
AddStepReport("清理冲突文件", false, new Dictionary<string, object>
|
||
{
|
||
{ "error", ex.Message }
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// ========== 步骤5: 复制 C# 到 Unity ==========
|
||
|
||
private bool Step5_CopyCSharp(Dictionary<string, string> 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<string, object>
|
||
{
|
||
{ "count", copiedCount }
|
||
});
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[FAIL] 步骤5失败: {ex.Message}");
|
||
Log(ex.StackTrace);
|
||
AddStepReport("复制C#到Unity", false, new Dictionary<string, object>
|
||
{
|
||
{ "error", ex.Message }
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// ========== 步骤6: 复制 Bytes 到 Unity ==========
|
||
|
||
private bool Step6_CopyBytes(Dictionary<string, string> 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<string, object>
|
||
{
|
||
{ "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<string, object>
|
||
{
|
||
{ "error", ex.Message }
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// ========== 步骤7: 生成 DR 文件 ==========
|
||
|
||
private bool Step7_GenerateDR(Dictionary<string, string> 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<string, object>
|
||
{
|
||
{ "total", generatedCount }
|
||
});
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log($"[FAIL] 步骤5失败: {ex.Message}");
|
||
Log(ex.StackTrace);
|
||
AddStepReport("生成DR文件", false, new Dictionary<string, object>
|
||
{
|
||
{ "error", ex.Message }
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析配置类文件
|
||
/// </summary>
|
||
private Dictionary<string, string> 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<int,\s*(?:global::[\w\.]+\.)?(\w+)>\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<string, string>
|
||
{
|
||
{ "class_name", className },
|
||
{ "item_type", itemType },
|
||
{ "dict_property", dictProperty }
|
||
};
|
||
}
|
||
catch
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析 Item 类
|
||
/// </summary>
|
||
private List<Dictionary<string, string>> ParseItemClass(string filePath)
|
||
{
|
||
List<Dictionary<string, string>> properties = new List<Dictionary<string, string>>();
|
||
|
||
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<string, string>
|
||
{
|
||
{ "type", finalType },
|
||
{ "name", name },
|
||
{ "original_type", type }, // 保存原始类型用于判断
|
||
{ "default", defaultValue }
|
||
});
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略错误
|
||
}
|
||
|
||
return properties;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成 DR 类代码
|
||
/// </summary>
|
||
private string GenerateDRCode(Dictionary<string, string> configInfo, List<Dictionary<string, string>> 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(" /// <summary>");
|
||
sb.AppendLine($" /// {className} 数据行");
|
||
sb.AppendLine(" /// </summary>");
|
||
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(" /// <summary>");
|
||
sb.AppendLine(" /// 唯一标识");
|
||
sb.AppendLine(" /// </summary>");
|
||
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(" /// <summary>");
|
||
sb.AppendLine($" /// {propName}");
|
||
sb.AppendLine(" /// </summary>");
|
||
|
||
// 特殊处理: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(" /// <summary>");
|
||
sb.AppendLine(" /// 从配置加载数据(优先使用传入的配置实例)");
|
||
sb.AppendLine(" /// </summary>");
|
||
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(" /// <summary>");
|
||
sb.AppendLine(" /// 直接设置配置数据(性能优化:跳过字典查询)");
|
||
sb.AppendLine(" /// </summary>");
|
||
sb.AppendLine($" public void SetConfigData({itemType} configData)");
|
||
sb.AppendLine(" {");
|
||
sb.AppendLine(" _configData = configData;");
|
||
sb.AppendLine(" }");
|
||
sb.AppendLine();
|
||
|
||
// ParseDataRow 方法(优化:使用 userData 传入的配置实例,或直接传入 Item)
|
||
sb.AppendLine(" /// <summary>");
|
||
sb.AppendLine(" /// 解析数据行(优化:使用 userData 传入的配置实例,避免重复调用 GetConfig)");
|
||
sb.AppendLine(" /// </summary>");
|
||
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<string, object> 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<int, " + itemType + ">;");
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取类型信息和默认值
|
||
/// </summary>
|
||
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<string, object> Details { get; set; }
|
||
}
|
||
}
|
||
}
|