MeowmentDesign/Assets/Scripts/DataTable/BywayDataTableHelper.cs
2026-02-01 15:37:46 +08:00

332 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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