using GameFramework.Resource; using System; using System.Collections.Generic; using Luban; using SepCore.Definition; using UnityEngine; using UnityGameFramework.Runtime; namespace SepCore.CustomComponent { /// /// Luban 数据表组件。 /// 生成的数据结构与枚举位于 Assets/GameMain/Scripts/Base/Gen。 /// 负责加载 Luban 导出的二进制数据表(.bytes)并构建 SepCore.Definition.Tables。 /// 注意:表名列表需与 SepCore.Definition.Tables 构造函数保持一致。 /// public class LubanComponent : GameFrameworkComponent { private static readonly string[] TableNames = new string[] { "tbenemyconfig", "tbentityconfig", "tbformattext", "tbglobalconfig", "tbgoodsconfig", "tblevelconfig", "tbleveldropconfig", "tblevelexperienceconfig", "tbleveluprewardconfig", "tbmusicconfig", "tbpropconfig", "tbroleconfig", "tbsceneconfig", "tbsoundconfig", "tbspriteconfig", "tbuiformconfig", "tbuisoundconfig", "tbweaponconfig", "tbstatconfig" }; private sealed class TableAccessor { public Func RowGetter; public Func StringRowGetter; public Func ListGetter; } private static readonly Dictionary TableAccessors = new() { { typeof(EnemyConfig), Accessor(tables => key => tables.TbEnemyConfig.GetOrDefault((EnemyType)key), tables => tables.TbEnemyConfig.DataList) }, { typeof(EntityConfig), StringAccessor(tables => key => tables.TbEntityConfig.GetOrDefault(key), tables => tables.TbEntityConfig.DataList) }, { typeof(FormatText), StringAccessor(tables => key => tables.TbFormatText.GetOrDefault(key), tables => tables.TbFormatText.DataList) }, { typeof(GoodsConfig), Accessor(tables => id => tables.TbGoodsConfig.GetOrDefault(id), tables => tables.TbGoodsConfig.DataList) }, { typeof(LevelConfig), Accessor(tables => id => tables.TbLevelConfig.GetOrDefault(id), tables => tables.TbLevelConfig.DataList) }, { typeof(LevelDropConfig), Accessor(tables => id => tables.TbLevelDropConfig.GetOrDefault(id), tables => tables.TbLevelDropConfig.DataList) }, { typeof(LevelExperienceConfig), StringAccessor(tables => key => tables.TbLevelExperienceConfig.GetOrDefault(key), tables => tables.TbLevelExperienceConfig.DataList) }, { typeof(LevelUpRewardConfig), Accessor(tables => id => tables.TbLevelUpRewardConfig.GetOrDefault(id), tables => tables.TbLevelUpRewardConfig.DataList) }, { typeof(MusicConfig), Accessor(tables => id => tables.TbMusicConfig.GetOrDefault(id), tables => tables.TbMusicConfig.DataList) }, { typeof(PropConfig), Accessor(tables => id => tables.TbPropConfig.GetOrDefault(id), tables => tables.TbPropConfig.DataList) }, { typeof(RoleConfig), StringAccessor(tables => key => tables.TbRoleConfig.GetOrDefault(key), tables => tables.TbRoleConfig.DataList) }, { typeof(SceneConfig), Accessor(tables => id => tables.TbSceneConfig.GetOrDefault((SceneType)id), tables => tables.TbSceneConfig.DataList) }, { typeof(SoundConfig), Accessor(tables => id => tables.TbSoundConfig.GetOrDefault(id), tables => tables.TbSoundConfig.DataList) }, { typeof(SpriteConfig), StringAccessor(tables => key => tables.TbSpriteConfig.GetOrDefault(key), tables => tables.TbSpriteConfig.DataList) }, { typeof(UIFormConfig), Accessor(tables => id => tables.TbUIFormConfig.GetOrDefault((UIFormType)id), tables => tables.TbUIFormConfig.DataList) }, { typeof(UISoundConfig), Accessor(tables => id => tables.TbUISoundConfig.GetOrDefault(id), tables => tables.TbUISoundConfig.DataList) }, { typeof(WeaponConfig), Accessor(tables => id => tables.TbWeaponConfig.GetOrDefault((WeaponType)id), tables => tables.TbWeaponConfig.DataList) }, { typeof(StatConfig), Accessor(tables => id => tables.TbStatConfig.GetOrDefault((StatType)id), tables => tables.TbStatConfig.DataList) }, }; private static TableAccessor Accessor(Func> rowGetterFactory, Func> listGetter) where TValue : BeanBase { return new TableAccessor { RowGetter = (tables, id) => rowGetterFactory(tables)(id), ListGetter = tables => listGetter(tables) }; } private static TableAccessor StringAccessor(Func> rowGetterFactory, Func> listGetter) where TValue : BeanBase { return new TableAccessor { StringRowGetter = (tables, key) => rowGetterFactory(tables)(key), ListGetter = tables => listGetter(tables) }; } private Dictionary _tableBytes = null; private int _remainCount = 0; private Action _onLoadSuccess = null; private Action _onLoadFailure = null; /// /// 获取 Luban 数据表管理器,加载完成后可用。 /// public Tables Tables { get; private set; } /// /// 获取数据表是否已加载完成。 /// public bool IsReady => Tables != null; public TbGlobalConfig Global => Tables.TbGlobalConfig; /// /// 按 id 获取数据行,未找到时返回 null。 /// /// 数据行类型(对应生成的 xxxConfig 类)。 /// 数据行主键。 /// 数据行,未找到时返回 null。 public T Get(int id) where T : class { if (Tables == null) { throw new InvalidOperationException("Luban data tables are not loaded yet."); } TableAccessor accessor; if (!TableAccessors.TryGetValue(typeof(T), out accessor)) { throw new NotSupportedException("No table getter is registered for type '" + typeof(T).FullName + "'."); } if (accessor.RowGetter == null) { if (accessor.StringRowGetter != null) { return (T)accessor.StringRowGetter(Tables, id.ToString()); } throw new NotSupportedException("Table for type '" + typeof(T).FullName + "' does not use an int key."); } return (T)accessor.RowGetter(Tables, id); } /// /// 按字符串主键获取数据行,未找到时返回 null。 /// /// 数据行类型(对应生成的 xxxConfig 类)。 /// 数据行字符串主键。 /// 数据行,未找到时返回 null。 public T Get(string key) where T : class { if (Tables == null) { throw new InvalidOperationException("Luban data tables are not loaded yet."); } TableAccessor accessor; if (!TableAccessors.TryGetValue(typeof(T), out accessor)) { throw new NotSupportedException("No table getter is registered for type '" + typeof(T).FullName + "'."); } if (accessor.StringRowGetter == null) { if (int.TryParse(key, out int parsedId) && accessor.RowGetter != null) { return (T)accessor.RowGetter(Tables, parsedId); } throw new NotSupportedException( "Table for type '" + typeof(T).FullName + "' does not use a string key."); } return (T)accessor.StringRowGetter(Tables, key); } /// /// 获取整个表的所有数据行。 /// /// 数据行类型(对应生成的 xxxConfig 类)。 /// 表内全部数据行,可安全遍历。 public IReadOnlyList GetTable() where T : class { if (Tables == null) { throw new InvalidOperationException("Luban data tables are not loaded yet."); } TableAccessor accessor; if (!TableAccessors.TryGetValue(typeof(T), out accessor)) { throw new NotSupportedException("No table getter is registered for type '" + typeof(T).FullName + "'."); } return (IReadOnlyList)accessor.ListGetter(Tables); } /// /// 异步加载所有数据表。 /// /// 加载成功回调。 /// 加载失败回调,参数为错误信息。 public void LoadTables(Action onSuccess, Action onFailure) { if (IsReady) { onSuccess?.Invoke(); return; } _onLoadSuccess = onSuccess; _onLoadFailure = onFailure; _tableBytes = new Dictionary(TableNames.Length); _remainCount = TableNames.Length; foreach (string tableName in TableNames) { GameEntry.Resource.LoadAsset(GetTableAssetPath(tableName), Constant.AssetPriority.DataTableAsset, new LoadAssetCallbacks(OnLoadTableSuccess, OnLoadTableFailure), tableName); } } private void OnLoadTableSuccess(string assetName, object asset, float duration, object userData) { string tableName = (string)userData; TextAsset textAsset = asset as TextAsset; if (textAsset == null) { OnLoadTableFailure(assetName, LoadResourceStatus.AssetError, "Table asset is invalid.", userData); return; } _tableBytes[tableName] = textAsset.bytes; if (--_remainCount > 0) { return; } try { Tables = new SepCore.Definition.Tables(name => new Luban.ByteBuf(_tableBytes[name])); _tableBytes = null; Log.Info("Load Luban data tables OK."); Action onLoadSuccess = _onLoadSuccess; _onLoadSuccess = null; _onLoadFailure = null; onLoadSuccess?.Invoke(); } catch (Exception exception) { Log.Error("Build Luban data tables failure with exception '{0}'.", exception); Action onLoadFailure = _onLoadFailure; _onLoadSuccess = null; _onLoadFailure = null; onLoadFailure?.Invoke(exception.ToString()); } } private void OnLoadTableFailure(string assetName, LoadResourceStatus status, string errorMessage, object userData) { Log.Error("Can not load data table '{0}' from '{1}' with error message '{2}'.", (string)userData, assetName, errorMessage); Action onLoadFailure = _onLoadFailure; _onLoadSuccess = null; _onLoadFailure = null; onLoadFailure?.Invoke(errorMessage); } private static string GetTableAssetPath(string tableName) { return GameFramework.Utility.Text.Format("Assets/GameMain/DataTables/{0}.bytes", tableName); } } }