332 lines
14 KiB
C#
332 lines
14 KiB
C#
//------------------------------------------------------------
|
||
// Byway Data Table Helper
|
||
// 基于 DefaultDataTableHelper 重新实现
|
||
// 核心改动:ParseData 方法从 Thrift 配置加载数据
|
||
//------------------------------------------------------------
|
||
|
||
using GameFramework;
|
||
using GameFramework.DataTable;
|
||
using System;
|
||
using UnityEngine;
|
||
using UnityGameFramework.Runtime;
|
||
|
||
namespace CrazyMaple
|
||
{
|
||
/// <summary>
|
||
/// Byway 数据表辅助器 - 从 Thrift 配置加载数据
|
||
/// </summary>
|
||
public class BywayDataTableHelper : DataTableHelperBase
|
||
{
|
||
private static readonly string BytesAssetExtension = ".bytes";
|
||
private ResourceComponent m_ResourceComponent = null;
|
||
|
||
/// <summary>
|
||
/// 读取数据表。
|
||
/// </summary>
|
||
public override bool ReadData(DataTableBase dataTable, string dataTableAssetName, object dataTableAsset, object userData)
|
||
{
|
||
TextAsset dataTableTextAsset = dataTableAsset as TextAsset;
|
||
if (dataTableTextAsset != null)
|
||
{
|
||
// Byway 系统始终使用 bytes 格式(Thrift 二进制)
|
||
return dataTable.ParseData(dataTableTextAsset.bytes, userData);
|
||
}
|
||
|
||
Log.Warning("Data table asset '{0}' is invalid.", dataTableAssetName);
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取数据表。
|
||
/// </summary>
|
||
public override bool ReadData(DataTableBase dataTable, string dataTableAssetName, byte[] dataTableBytes, int startIndex, int length, object userData)
|
||
{
|
||
// Byway 系统始终使用 bytes 格式(Thrift 二进制)
|
||
return dataTable.ParseData(dataTableBytes, startIndex, length, userData);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析数据表(字符串格式 - Byway 不支持)
|
||
/// </summary>
|
||
public override bool ParseData(DataTableBase dataTable, string dataTableString, object userData)
|
||
{
|
||
Log.Error("Byway data table system does not support string format. Use Thrift bytes instead.");
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析数据表(核心方法 - 从 Thrift 配置加载)
|
||
/// </summary>
|
||
public override bool ParseData(DataTableBase dataTable, byte[] dataTableBytes, int startIndex, int length, object userData)
|
||
{
|
||
var timer = System.Diagnostics.Stopwatch.StartNew();
|
||
|
||
try
|
||
{
|
||
// 获取 DataRow 类型
|
||
Type dataRowType = dataTable.Type;
|
||
string dataRowTypeName = dataRowType.Name;
|
||
|
||
// 推断 Thrift 配置类型名(去除 DR 前缀)
|
||
// 例如:DRMergeData -> MergeData
|
||
string configTypeName = dataRowTypeName.StartsWith("DR")
|
||
? dataRowTypeName.Substring(2)
|
||
: dataRowTypeName;
|
||
|
||
// 通过反射获取 Thrift 配置类型
|
||
Type configType = FindThriftConfigType(configTypeName);
|
||
if (configType == null)
|
||
{
|
||
Log.Error("Can not find Thrift config type '{0}' for data row type '{1}'.", configTypeName, dataRowTypeName);
|
||
return false;
|
||
}
|
||
|
||
// 调用 ConfigManager.GetConfig<T>() 获取配置对象
|
||
var getConfigMethod = typeof(Byway.Config.ConfigManager)
|
||
.GetMethod("GetConfig")
|
||
.MakeGenericMethod(configType);
|
||
|
||
object configInstance = getConfigMethod.Invoke(Byway.Config.ConfigManager.Instance, null);
|
||
if (configInstance == null)
|
||
{
|
||
Log.Error("Can not get config instance for type '{0}'.", configTypeName);
|
||
return false;
|
||
}
|
||
|
||
// 获取配置字典
|
||
var dictData = GetConfigDictionary(configInstance, configTypeName);
|
||
if (dictData == null)
|
||
{
|
||
Log.Error("Can not get config dictionary for type '{0}'.", configTypeName);
|
||
return false;
|
||
}
|
||
|
||
// 性能优化:不预先创建所有 DataRow,而是缓存配置字典供按需查询
|
||
// 对于几千行的大表,这样可以避免启动时创建几千个 DataRow 对象
|
||
var dict = dictData as System.Collections.IDictionary;
|
||
if (dict == null)
|
||
{
|
||
Log.Error("Config dictionary is not IDictionary type.");
|
||
return false;
|
||
}
|
||
|
||
// 将配置实例缓存到 DataTable 的 userData 中,供后续按需获取
|
||
// 格式:{ "ConfigInstance": configInstance, "ConfigDict": dict }
|
||
var cacheData = new System.Collections.Generic.Dictionary<string, object>
|
||
{
|
||
{ "ConfigInstance", configInstance },
|
||
{ "ConfigDict", dictData },
|
||
{ "OriginalUserData", userData }
|
||
};
|
||
|
||
// 性能优化选项:反射路径实测反而更慢,禁用
|
||
// 标准路径(ParseDataRow + ConfigDict)已经是最优解
|
||
bool useDirectConstruction = false; // 禁用反射优化路径
|
||
|
||
int successCount = 0;
|
||
int failCount = 0;
|
||
|
||
// Debug.LogError($"[性能测试] 表 {dataRowTypeName} 开始加载,行数: {dict.Count}, 模式: 标准路径(ConfigDict直接获取)");
|
||
var loadTimer = System.Diagnostics.Stopwatch.StartNew();
|
||
|
||
if (useDirectConstruction)
|
||
{
|
||
// 【性能最优路径】直接创建 DataRow 并设置数据,完全跳过 ParseDataRow
|
||
// Log.Info("Large table detected ({0} rows), using direct construction mode.", dict.Count);
|
||
|
||
foreach (System.Collections.DictionaryEntry entry in dict)
|
||
{
|
||
try
|
||
{
|
||
int id = (int)entry.Key;
|
||
object itemData = entry.Value;
|
||
|
||
// 直接创建 DataRow 实例(不走 AddDataRow)
|
||
var dataRow = Activator.CreateInstance(dataTable.Type) as DataRowBase;
|
||
if (dataRow == null)
|
||
{
|
||
failCount++;
|
||
continue;
|
||
}
|
||
|
||
// 调用 SetConfigData 方法直接设置数据(跳过所有查询)
|
||
var setDataMethod = dataTable.Type.GetMethod("SetConfigData");
|
||
if (setDataMethod != null)
|
||
{
|
||
setDataMethod.Invoke(dataRow, new object[] { itemData });
|
||
|
||
// 使用反射调用 DataTable 的内部 AddDataRow 方法
|
||
var addMethod = dataTable.GetType().GetMethod("InternalAddDataRow",
|
||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||
|
||
if (addMethod != null)
|
||
{
|
||
addMethod.Invoke(dataTable, new object[] { dataRow });
|
||
successCount++;
|
||
}
|
||
else
|
||
{
|
||
// 兜底:使用 AddDataRow(会触发 ParseDataRow,但数据已设置好)
|
||
if (dataTable.AddDataRow(id.ToString(), null))
|
||
{
|
||
successCount++;
|
||
}
|
||
else
|
||
{
|
||
failCount++;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Log.Warning("SetConfigData method not found, fallback to AddDataRow");
|
||
// 降级到标准模式
|
||
useDirectConstruction = false;
|
||
break;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error("Direct construction failed for entry: {0}", ex.Message);
|
||
failCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 【标准路径】通过 AddDataRow + ParseDataRow
|
||
if (!useDirectConstruction || (successCount == 0 && dict.Count > 0))
|
||
{
|
||
Log.Info("Using standard AddDataRow mode ({0} rows).", dict.Count);
|
||
successCount = 0;
|
||
failCount = 0;
|
||
|
||
foreach (System.Collections.DictionaryEntry entry in dict)
|
||
{
|
||
try
|
||
{
|
||
int id = (int)entry.Key;
|
||
|
||
// 调用 DataTable.AddDataRow(int id)
|
||
// DataRow 的 ParseDataRow 方法会被调用,传入 id 和配置实例
|
||
if (!dataTable.AddDataRow(id.ToString(), cacheData))
|
||
{
|
||
Log.Warning("Add data row failed for id '{0}'.", id);
|
||
failCount++;
|
||
}
|
||
else
|
||
{
|
||
successCount++;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Log.Error("Add data row exception: {0}", ex.Message);
|
||
failCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
loadTimer.Stop();
|
||
// Debug.LogError($"[性能测试] 表 {dataRowTypeName} 加载完成,成功: {successCount}, 失败: {failCount}, 耗时: {loadTimer.ElapsedMilliseconds}ms ({loadTimer.Elapsed.TotalSeconds:F3}秒)");
|
||
|
||
timer.Stop();
|
||
// Debug.LogError($"[性能测试] 表 {dataRowTypeName} 总耗时(含配置获取): {timer.ElapsedMilliseconds}ms ({timer.Elapsed.TotalSeconds:F3}秒)");
|
||
|
||
return failCount == 0;
|
||
}
|
||
catch (Exception exception)
|
||
{
|
||
Log.Error("Parse Thrift data table failed with exception: {0}\n{1}", exception.Message, exception.StackTrace);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查找 Thrift 配置类型
|
||
/// </summary>
|
||
private Type FindThriftConfigType(string typeName)
|
||
{
|
||
// 在 Byway.Thrift.Data 命名空间中查找
|
||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||
foreach (var assembly in assemblies)
|
||
{
|
||
Type type = assembly.GetType($"Byway.Thrift.Data.{typeName}");
|
||
if (type != null)
|
||
{
|
||
return type;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取配置字典
|
||
/// </summary>
|
||
private object GetConfigDictionary(object config, string configTypeName)
|
||
{
|
||
if (config == null) return null;
|
||
|
||
Type configType = config.GetType();
|
||
|
||
// 尝试多种可能的字典属性名
|
||
string[] possibleNames = new string[]
|
||
{
|
||
configTypeName + "s", // MergeData -> MergeDatas
|
||
configTypeName.ToLower() + "s", // MergeData -> mergedatas
|
||
char.ToLower(configTypeName[0]) + configTypeName.Substring(1) + "s", // MergeData -> mergeDatas
|
||
"Items",
|
||
"Data",
|
||
"Configs"
|
||
};
|
||
|
||
foreach (string name in possibleNames)
|
||
{
|
||
var property = configType.GetProperty(name);
|
||
if (property != null)
|
||
{
|
||
object value = property.GetValue(config);
|
||
if (value != null)
|
||
{
|
||
return value;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 查找第一个 Dictionary 类型的属性
|
||
foreach (var property in configType.GetProperties())
|
||
{
|
||
Type propType = property.PropertyType;
|
||
if (propType.IsGenericType &&
|
||
propType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.Dictionary<,>))
|
||
{
|
||
object value = property.GetValue(config);
|
||
if (value != null)
|
||
{
|
||
return value;
|
||
}
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放数据表资源
|
||
/// </summary>
|
||
public override void ReleaseDataAsset(DataTableBase dataTable, object dataTableAsset)
|
||
{
|
||
m_ResourceComponent.UnloadAsset(dataTableAsset);
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
m_ResourceComponent = UnityGameFramework.Runtime.GameEntry.GetComponent<ResourceComponent>();
|
||
if (m_ResourceComponent == null)
|
||
{
|
||
Log.Fatal("Resource component is invalid.");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|