using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OfficeOpenXml;
using Thrift.Protocol;
using Thrift.Transport.Client;
using System.Threading;
namespace ThriftPipelineTools
{
///
/// 快速生成 Bytes 工具(简化版)
/// 只生成 AllConfigs.bytes,不生成 C# 和 DR
/// 适用于表结构未变化,只需要更新数据的场景
///
public class ThriftBytesGeneratorEditor : EditorWindow
{
// ========== 版本信息 ==========
private const string VERSION = "v1.0.0 (Bytes Only)";
// ========== 配置Key(使用EditorPrefs) ==========
private const string PREF_KEY_DOCS_PATH = "ThriftPipeline_DocsPath";
// ========== 路径配置 ==========
private string docsProjectPath = ""; // Docs 项目路径
// ========== UI 状态 ==========
private Vector2 scrollPosition;
private Vector2 logScrollPosition;
private StringBuilder logBuilder = new StringBuilder();
private bool isExecuting = false;
// ========== 进度控制 ==========
private float progress = 0f;
private string currentStep = "";
[MenuItem("配置表pipeline/Thrift/快速生成 Bytes(简化版)")]
public static void ShowWindow()
{
var window = GetWindow("快速生成 Bytes");
window.minSize = new Vector2(800, 600);
window.Show();
}
private void OnEnable()
{
LoadConfig();
}
private void OnGUI()
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
EditorGUILayout.Space(10);
EditorGUILayout.LabelField($"快速生成 Bytes 工具 - {VERSION}", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"此工具只生成 AllConfigs.bytes 文件,不生成 C# 和 DR\n" +
"适用于:表结构未变化,只需要更新配置数据的场景\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(
"只需配置 Docs 项目路径(包含 Excel 配置和 cfg_txt.json)",
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();
// 显示派生路径预览
if (!string.IsNullOrEmpty(docsProjectPath))
{
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($" 输出到 Unity: {paths["unity_bytes_dir"]}", EditorStyles.miniLabel);
}
EditorGUILayout.EndVertical();
}
///
/// 绘制执行区域
///
private void DrawExecutionSection()
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("执行控制", EditorStyles.boldLabel);
GUI.enabled = !isExecuting && ValidatePaths();
if (GUILayout.Button("⚡ 快速生成 AllConfigs.bytes", GUILayout.Height(40)))
{
GenerateBytes();
}
GUI.enabled = true;
if (isExecuting)
{
EditorGUILayout.Space(5);
EditorGUILayout.LabelField($"当前步骤: {currentStep}");
EditorGUI.ProgressBar(EditorGUILayout.GetControlRect(GUILayout.Height(20)), progress, "执行中...");
}
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
{
docsProjectPath = EditorPrefs.GetString(PREF_KEY_DOCS_PATH, "");
}
catch (Exception ex)
{
Debug.LogError($"加载配置失败: {ex.Message}");
}
}
///
/// 保存配置(使用 EditorPrefs,本地保存,不会被git提交)
///
private void SaveConfig()
{
try
{
EditorPrefs.SetString(PREF_KEY_DOCS_PATH, docsProjectPath);
}
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");
}
// Unity 项目相关路径(当前项目)
string assetsPath = Application.dataPath;
paths["unity_bytes_dir"] = Path.Combine(assetsPath, "Design_SubModule", "ConfigData");
return paths;
}
///
/// 验证路径
///
private bool ValidatePaths()
{
if (string.IsNullOrEmpty(docsProjectPath))
return false;
var paths = GetDerivedPaths();
if (!File.Exists(paths["cfg_json"]))
return false;
if (!Directory.Exists(paths["config_dir"]))
return false;
return true;
}
///
/// 日志输出
///
private void Log(string message)
{
logBuilder.AppendLine(message);
Repaint();
}
///
/// 生成 Bytes 文件
///
private async void GenerateBytes()
{
isExecuting = true;
logBuilder.Clear();
progress = 0f;
DateTime startTime = DateTime.Now;
Log("================================================================================");
Log($"开始生成 AllConfigs.bytes ({VERSION})");
Log("================================================================================");
Log("");
try
{
var paths = GetDerivedPaths();
currentStep = "读取配置并生成 bytes";
progress = 0.3f;
Repaint();
if (!await GenerateBytesFile(paths))
{
Log("[FAIL] 生成失败");
EditorUtility.DisplayDialog("失败", "生成 AllConfigs.bytes 失败,请查看日志", "确定");
return;
}
progress = 1f;
currentStep = "完成";
Repaint();
DateTime endTime = DateTime.Now;
TimeSpan duration = endTime - startTime;
Log("");
Log("================================================================================");
Log($"✅ 生成完成!");
Log($" 耗时: {duration.TotalSeconds:F2} 秒");
Log($" 输出: {paths["unity_bytes_dir"]}/AllConfigs.bytes");
Log("================================================================================");
EditorUtility.DisplayDialog("成功", $"AllConfigs.bytes 生成完成!\n耗时: {duration.TotalSeconds:F2} 秒", "确定");
}
catch (Exception ex)
{
Log($"\n[FAIL] 执行失败: {ex.Message}");
Log(ex.StackTrace);
EditorUtility.DisplayDialog("错误", $"执行失败:\n{ex.Message}", "确定");
}
finally
{
isExecuting = false;
AssetDatabase.Refresh();
Repaint();
}
}
///
/// 生成 Bytes 文件
///
private async Task GenerateBytesFile(Dictionary paths)
{
Log("【生成 AllConfigs.bytes】");
Log("--------------------------------------------------------------------------------");
try
{
string cfgJsonPath = paths["cfg_json"];
string configDir = paths["config_dir"];
string bytesOutputDir = paths["unity_bytes_dir"];
// 创建输出目录
if (!Directory.Exists(bytesOutputDir))
Directory.CreateDirectory(bytesOutputDir);
// 加载程序集
Log(" [1/3] 加载程序集");
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] 找不到 Byway.Thrift.Data.AllConfigs 程序集");
Log(" 请确保已经执行过完整流程,生成了 C# 类");
return false;
}
Log(" [OK] 程序集加载成功");
// 读取配置
Log(" [2/3] 读取配置并填充数据");
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
Log($"\n [3/3] 序列化 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;
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),可能大部分配置未序列化!");
}
}
}
}
}
Log($"\n生成完成: 成功 {successCount} 个配置");
return true;
}
catch (Exception ex)
{
Log($"[FAIL] 生成失败: {ex.Message}");
Log(ex.StackTrace);
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)
{
Log($" [ERROR] 找不到类型: {structName} 或 {structName}Item");
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)
{
Log($" [ERROR] 工作表为空");
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;
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;
if (cellValue == null)
{
if (property.PropertyType == typeof(string))
{
convertedValue = "";
}
else if (property.PropertyType.IsValueType)
{
convertedValue = Activator.CreateInstance(property.PropertyType);
}
else
{
continue;
}
}
else
{
convertedValue = ConvertCellValue(cellValue, property.PropertyType);
}
property.SetValue(itemInstance, convertedValue);
if (fieldName.ToLower() == "id" && convertedValue is int)
idValue = (int)convertedValue;
}
catch (Exception ex)
{
Log($" [WARN] 行{row} 字段{fieldName}转换失败: {ex.Message}");
}
}
}
if (idValue.HasValue)
{
var addMethod = dictType.GetMethod("Add");
addMethod.Invoke(dictInstance, new object[] { idValue.Value, itemInstance });
filledItemCount++;
}
}
Log($" 读取了 {filledItemCount} 条数据");
}
// 设置字典属性
string dictPropertyName = char.ToUpper(structName[0]) + structName.Substring(1).ToLower() + "s";
var dictProperty = configType.GetProperty(dictPropertyName);
if (dictProperty != null)
{
dictProperty.SetValue(configInstance, dictInstance);
}
else
{
Log($" [ERROR] 未找到字典属性: {dictPropertyName}");
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;
}
Log($" [OK] 成功");
}
else
{
Log($" [ERROR] AllConfigs中未找到属性: {structName}");
return false;
}
return true;
}
catch (Exception ex)
{
Log($" [ERROR] {structName}: {ex.Message}");
return false;
}
}
///
/// 清理并转换字段名为PascalCase
///
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;
}
///
/// 转换单元格值
///
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;
}
}
}
}