From 6397cc03b27f62101a28baef161ecf0bf6f237f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Tue, 31 Mar 2026 17:25:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A1=86=E6=9E=B6=E5=A4=A7=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.进步优化UI系统 加载问题 性能问题 Canvas重绘问题 边界处理问题 2.优化对象池和游戏对象池的性能 游戏对象池根据窗口 策略定期清理 3.优化整个AppService 和ServiceWorld结构 固定三大类 具体参考代码 --- .../Localization/LocalizationAiWriteTool.cs | 485 ++++++++++ .../LocalizationAiWriteTool.cs.meta | 11 + Runtime/ABase/Base/Service/Core/AppScope.cs | 22 +- .../ABase/Base/Service/Core/AppServices.cs | 43 +- .../ABase/Base/Service/Core/ServiceContext.cs | 39 +- .../ABase/Base/Service/Core/ServiceWorld.cs | 119 ++- .../Base/Service/Unity/AppServiceRoot.cs | 4 +- .../Service/Unity/MonoServiceBehaviour.cs | 58 +- .../GameObjectPool/GameObjectPoolManager.cs | 70 +- .../ABase/GameObjectPool/IResourceLoader.cs | 53 +- .../ABase/GameObjectPool/RuntimePoolModels.cs | 31 + Runtime/ABase/ModuleDynamicBind.cs | 24 +- .../ABase/ObjectPool/IObjectPoolService.cs | 743 +-------------- .../ABase/ObjectPool/ObjectPoolComponent.cs | 15 +- Runtime/ABase/ObjectPool/ObjectPoolService.cs | 38 + Runtime/ABase/Utility/Utility.Unity.cs | 240 ++--- Runtime/Audio/AudioComponent.cs | 11 +- Runtime/Debugger/DebuggerComponent.cs | 61 +- Runtime/Localization/LocalizationComponent.cs | 130 ++- .../ScriptableObject/GameLocaizationTable.cs | 48 +- .../Service/ILocalizationService.cs | 680 +++++++------- .../Service/LocalizationService.cs | 846 ++++++++++-------- Runtime/Procedure/IProcedureService.cs | 22 +- Runtime/Procedure/ProcedureComponent.cs | 3 +- Runtime/Procedure/ProcedureService.cs | 107 +-- .../ResourceExtComponent.Resource.cs | 5 +- .../Extension/ResourceExtComponent.cs | 65 +- Runtime/Resource/Resource/IResourceService.cs | 6 +- .../Resource/Resource/ResourceComponent.cs | 38 +- .../Resource/Resource/ResourceService.Pool.cs | 3 +- Runtime/Resource/Resource/ResourceService.cs | 6 +- Runtime/Scene/ISceneService.cs | 64 +- Runtime/Scene/SceneComponent.cs | 8 +- Runtime/Scene/SceneService.cs | 419 ++++----- Runtime/Timer/TimerComponent.cs | 3 +- Runtime/UI/Constant/UIHolderFactory.cs | 57 +- Runtime/UI/Constant/UIMetadataFactory.cs | 9 +- Runtime/UI/Manager/UIService.Open.cs | 188 +++- Runtime/UI/UIBase/UITabWindow.cs | 7 +- Runtime/UI/UIBase/UIWidget.cs | 4 + Runtime/UI/UIBase/UIWindow.cs | 5 +- Runtime/UI/UIComponent.cs | 21 +- 42 files changed, 2678 insertions(+), 2133 deletions(-) create mode 100644 Editor/Localization/LocalizationAiWriteTool.cs create mode 100644 Editor/Localization/LocalizationAiWriteTool.cs.meta diff --git a/Editor/Localization/LocalizationAiWriteTool.cs b/Editor/Localization/LocalizationAiWriteTool.cs new file mode 100644 index 0000000..8a2539e --- /dev/null +++ b/Editor/Localization/LocalizationAiWriteTool.cs @@ -0,0 +1,485 @@ +using System; +using System.Collections.Generic; +using System.Text; +using AlicizaX.Localization.Runtime; +using UnityEditor; +using UnityEngine; + +namespace AlicizaX.Localization.Editor +{ + [Serializable] + public sealed class LocalizationAiWriteRequest + { + public string TableAssetPath; + public string SectionName = "AI"; + public string Key; + public bool AllowUpdate = true; + public bool AutoCreateSection = true; + public bool AutoCreateLanguageAsset = true; + public bool FillMissingLanguagesWithEmpty = true; + public bool SaveAssets = true; + public bool HotReloadWhenPlaying = true; + public List Translations = new(); + } + + [Serializable] + public sealed class LocalizationAiTranslation + { + public string Language; + public string Text; + } + + [Serializable] + public sealed class LocalizationAiWriteResult + { + public bool Success; + public string TableAssetPath; + public string SectionName; + public string Key; + public string RuntimeKey; + public bool CreatedSection; + public bool CreatedEntry; + public bool HotReloaded; + public List UpdatedLanguages = new(); + public List Warnings = new(); + public string Message; + } + + public static class LocalizationAiWriteTool + { + private const string DefaultSectionName = "AI"; + private const string LastSelectedTableKey = "LastSelectedGameLocaizationTable"; + + public static LocalizationAiWriteResult Execute(LocalizationAiWriteRequest request) + { + LocalizationAiWriteResult result = new(); + if (request == null) + { + result.Message = "Request is null."; + return result; + } + + string tablePath = request.TableAssetPath; + if (string.IsNullOrWhiteSpace(tablePath)) + { + tablePath = EditorPrefs.GetString(LastSelectedTableKey, string.Empty); + } + + if (string.IsNullOrWhiteSpace(tablePath)) + { + result.Message = "TableAssetPath is empty."; + return result; + } + + GameLocaizationTable table = AssetDatabase.LoadAssetAtPath(tablePath); + if (table == null) + { + result.Message = $"Can not load GameLocaizationTable from path: {tablePath}"; + result.TableAssetPath = tablePath; + return result; + } + + result.TableAssetPath = tablePath; + + string sectionName = request.SectionName; + string itemKey = request.Key; + ParseAndNormalizeKey(ref sectionName, ref itemKey); + + if (string.IsNullOrEmpty(sectionName)) + { + sectionName = DefaultSectionName; + } + + if (string.IsNullOrEmpty(itemKey)) + { + result.Message = "Key is empty after normalization."; + return result; + } + + if (table.TableSheet == null) + { + table.TableSheet = new List(); + } + + if (table.Languages == null) + { + table.Languages = new List(); + } + + int sectionIndex = FindSectionIndex(table, sectionName); + if (sectionIndex < 0) + { + if (!request.AutoCreateSection) + { + result.Message = $"Section '{sectionName}' does not exist."; + return result; + } + + GameLocaizationTable.TableData newSection = new(sectionName, GenerateUniqueSectionId(table)); + table.TableSheet.Add(newSection); + sectionIndex = table.TableSheet.Count - 1; + result.CreatedSection = true; + } + + GameLocaizationTable.TableData section = table.TableSheet[sectionIndex]; + if (section.SectionSheet == null) + { + section.SectionSheet = new List(); + } + + int entryIndex = FindEntryIndex(section, itemKey); + bool createdEntry = false; + if (entryIndex < 0) + { + GameLocaizationTable.SheetItem newItem = new(itemKey, GenerateUniqueEntryId(table), true); + section.SectionSheet.Add(newItem); + entryIndex = section.SectionSheet.Count - 1; + createdEntry = true; + result.CreatedEntry = true; + } + else if (!request.AllowUpdate) + { + result.Message = $"Key '{itemKey}' already exists in section '{sectionName}'."; + return result; + } + + GameLocaizationTable.SheetItem entry = section.SectionSheet[entryIndex]; + string runtimeKey = BuildRuntimeKey(section.SectionName, entry.Key); + + Dictionary translationMap = BuildTranslationMap(request.Translations, result.Warnings); + EnsureEntryExistsForAllLanguages(table, section, entry, runtimeKey, translationMap, request, result); + + table.TableSheet[sectionIndex] = section; + + table.InvalidateLanguageLookup(); + EditorUtility.SetDirty(table); + if (request.SaveAssets) + { + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + + if (request.HotReloadWhenPlaying && + Application.isPlaying && + AppServices.TryGet(out var localizationService)) + { + localizationService.ReloadLocalizationConfig(table); + result.HotReloaded = true; + } + + result.Success = true; + result.SectionName = section.SectionName; + result.Key = entry.Key; + result.RuntimeKey = runtimeKey; + result.Message = createdEntry + ? $"Added localization entry '{runtimeKey}'." + : $"Updated localization entry '{runtimeKey}'."; + return result; + } + + public static string ExecuteJson(string json) + { + LocalizationAiWriteRequest request = Utility.Json.ToObject(json); + LocalizationAiWriteResult result = Execute(request); + return Utility.Json.ToJson(result); + } + + private static void EnsureEntryExistsForAllLanguages( + GameLocaizationTable table, + GameLocaizationTable.TableData section, + GameLocaizationTable.SheetItem entry, + string runtimeKey, + Dictionary translationMap, + LocalizationAiWriteRequest request, + LocalizationAiWriteResult result) + { + for (int i = 0; i < table.Languages.Count; i++) + { + LocalizationLanguage language = table.Languages[i]; + if (language == null) + { + continue; + } + + UpsertLanguageEntry( + language, + section.Id, + entry.Id, + runtimeKey, + translationMap.TryGetValue(language.LanguageName, out string text) ? text : string.Empty, + translationMap.ContainsKey(language.LanguageName) || request.FillMissingLanguagesWithEmpty); + + EditorUtility.SetDirty(language); + result.UpdatedLanguages.Add(language.LanguageName); + } + + foreach (KeyValuePair pair in translationMap) + { + if (HasLanguage(table, pair.Key)) + { + continue; + } + + if (!request.AutoCreateLanguageAsset) + { + result.Warnings.Add($"Language asset '{pair.Key}' does not exist in table '{table.name}'."); + continue; + } + + LocalizationLanguage language = CreateLanguageAsset(table, pair.Key); + UpsertLanguageEntry(language, section.Id, entry.Id, runtimeKey, pair.Value, true); + EditorUtility.SetDirty(language); + result.UpdatedLanguages.Add(language.LanguageName); + } + } + + private static void UpsertLanguageEntry( + LocalizationLanguage language, + int sectionId, + int entryId, + string runtimeKey, + string value, + bool shouldWrite) + { + if (language.Strings == null) + { + language.Strings = new List(); + } + + int index = FindLanguageStringIndex(language, sectionId, entryId); + if (index >= 0) + { + LocalizationLanguage.LocalizationString item = language.Strings[index]; + item.Key = runtimeKey; + if (shouldWrite) + { + item.Value = value ?? string.Empty; + } + + language.Strings[index] = item; + return; + } + + if (!shouldWrite) + { + return; + } + + language.Strings.Add(new LocalizationLanguage.LocalizationString + { + SectionId = sectionId, + EntryId = entryId, + Key = runtimeKey, + Value = value ?? string.Empty + }); + } + + private static LocalizationLanguage CreateLanguageAsset(GameLocaizationTable table, string languageName) + { + LocalizationLanguage language = ScriptableObject.CreateInstance(); + language.name = languageName; + language.LanguageName = languageName; + language.Strings = new List(); + AssetDatabase.AddObjectToAsset(language, table); + table.Languages.Add(language); + table.InvalidateLanguageLookup(); + EditorUtility.SetDirty(table); + return language; + } + + private static Dictionary BuildTranslationMap( + List translations, + List warnings) + { + Dictionary map = new(StringComparer.Ordinal); + if (translations == null) + { + return map; + } + + for (int i = 0; i < translations.Count; i++) + { + LocalizationAiTranslation translation = translations[i]; + if (translation == null || string.IsNullOrWhiteSpace(translation.Language)) + { + continue; + } + + string normalizedLanguage = NormalizePart(translation.Language); + if (string.IsNullOrEmpty(normalizedLanguage)) + { + continue; + } + + if (map.ContainsKey(normalizedLanguage)) + { + warnings.Add($"Duplicate language '{normalizedLanguage}' found. Last value wins."); + } + + map[normalizedLanguage] = translation.Text ?? string.Empty; + } + + return map; + } + + private static bool HasLanguage(GameLocaizationTable table, string languageName) + { + for (int i = 0; i < table.Languages.Count; i++) + { + LocalizationLanguage language = table.Languages[i]; + if (language != null && string.Equals(language.LanguageName, languageName, StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + private static int FindSectionIndex(GameLocaizationTable table, string sectionName) + { + for (int i = 0; i < table.TableSheet.Count; i++) + { + if (string.Equals(table.TableSheet[i].SectionName, sectionName, StringComparison.Ordinal)) + { + return i; + } + } + + return -1; + } + + private static int FindEntryIndex(GameLocaizationTable.TableData section, string itemKey) + { + for (int i = 0; i < section.SectionSheet.Count; i++) + { + if (string.Equals(section.SectionSheet[i].Key, itemKey, StringComparison.Ordinal)) + { + return i; + } + } + + return -1; + } + + private static int FindLanguageStringIndex(LocalizationLanguage language, int sectionId, int entryId) + { + for (int i = 0; i < language.Strings.Count; i++) + { + LocalizationLanguage.LocalizationString item = language.Strings[i]; + if (item.SectionId == sectionId && item.EntryId == entryId) + { + return i; + } + } + + return -1; + } + + private static int GenerateUniqueSectionId(GameLocaizationTable table) + { + return GenerateUniqueId(table, true); + } + + private static int GenerateUniqueEntryId(GameLocaizationTable table) + { + return GenerateUniqueId(table, false); + } + + private static int GenerateUniqueId(GameLocaizationTable table, bool checkSectionIds) + { + int candidate = Mathf.Abs(Guid.NewGuid().GetHashCode()); + while (candidate == 0 || ContainsId(table, candidate, checkSectionIds)) + { + candidate = Mathf.Abs(Guid.NewGuid().GetHashCode()); + } + + return candidate; + } + + private static bool ContainsId(GameLocaizationTable table, int id, bool checkSectionIds) + { + for (int i = 0; i < table.TableSheet.Count; i++) + { + GameLocaizationTable.TableData section = table.TableSheet[i]; + if (checkSectionIds && section.Id == id) + { + return true; + } + + if (section.SectionSheet == null) + { + continue; + } + + for (int j = 0; j < section.SectionSheet.Count; j++) + { + if (!checkSectionIds && section.SectionSheet[j].Id == id) + { + return true; + } + } + } + + return false; + } + + private static string BuildRuntimeKey(string sectionName, string itemKey) + { + return string.Concat(sectionName, ".", itemKey); + } + + private static void ParseAndNormalizeKey(ref string sectionName, ref string itemKey) + { + if (!string.IsNullOrWhiteSpace(itemKey)) + { + int separatorIndex = itemKey.IndexOf('.'); + if (separatorIndex > 0 && separatorIndex < itemKey.Length - 1) + { + sectionName = itemKey.Substring(0, separatorIndex); + itemKey = itemKey.Substring(separatorIndex + 1); + } + } + + sectionName = NormalizePart(sectionName); + itemKey = NormalizePart(itemKey); + } + + private static string NormalizePart(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return string.Empty; + } + + StringBuilder builder = new(value.Length); + for (int i = 0; i < value.Length; i++) + { + char current = value[i]; + if (char.IsLetterOrDigit(current) || current == '_') + { + builder.Append(current); + continue; + } + + if (char.IsWhiteSpace(current) || current == '-' || current == '/') + { + builder.Append('_'); + } + } + + while (builder.Length > 0 && builder[0] == '_') + { + builder.Remove(0, 1); + } + + while (builder.Length > 0 && builder[builder.Length - 1] == '_') + { + builder.Remove(builder.Length - 1, 1); + } + + return builder.ToString(); + } + } +} + diff --git a/Editor/Localization/LocalizationAiWriteTool.cs.meta b/Editor/Localization/LocalizationAiWriteTool.cs.meta new file mode 100644 index 0000000..1c418e7 --- /dev/null +++ b/Editor/Localization/LocalizationAiWriteTool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 511aa2d0442ccf746aa6dd266d5197d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Base/Service/Core/AppScope.cs b/Runtime/ABase/Base/Service/Core/AppScope.cs index 6147de2..06ec262 100644 --- a/Runtime/ABase/Base/Service/Core/AppScope.cs +++ b/Runtime/ABase/Base/Service/Core/AppScope.cs @@ -1,11 +1,25 @@ namespace AlicizaX { - /// - /// 框架内置的 App Scope 标记类,生命周期与 ServiceWorld 相同。 - /// + public static class ServiceDomainOrder + { + public const int App = -10000; + public const int Scene = -5000; + public const int Gameplay = 0; + } + public sealed class AppScope : IScope { - public int Order => -10000; + public int Order => ServiceDomainOrder.App; + } + + public sealed class SceneScope : IScope + { + public int Order => ServiceDomainOrder.Scene; + } + + public sealed class GameplayScope : IScope + { + public int Order => ServiceDomainOrder.Gameplay; } public interface IScope diff --git a/Runtime/ABase/Base/Service/Core/AppServices.cs b/Runtime/ABase/Base/Service/Core/AppServices.cs index 2b76544..79879d6 100644 --- a/Runtime/ABase/Base/Service/Core/AppServices.cs +++ b/Runtime/ABase/Base/Service/Core/AppServices.cs @@ -7,6 +7,8 @@ namespace AlicizaX private static ServiceWorld _world; public static bool HasWorld => _world != null; + public static bool HasScene => _world != null && _world.HasScene; + public static bool HasGameplay => _world != null && _world.HasGameplay; public static ServiceWorld World { @@ -18,36 +20,45 @@ namespace AlicizaX } } - public static ServiceScope App => World.AppScope; + public static ServiceScope App => World.App; - public static ServiceWorld EnsureWorld(int appScopeOrder = -10000) + public static ServiceScope Scene => World.Scene; + + public static ServiceScope Gameplay => World.Gameplay; + + public static ServiceWorld EnsureWorld(int appScopeOrder = ServiceDomainOrder.App) { if (_world == null) _world = new ServiceWorld(appScopeOrder); return _world; } - // ── Scope 管理 ────────────────────────────────────────────────────────── + public static ServiceScope EnsureScene(int order = ServiceDomainOrder.Scene) + => World.EnsureScene(order); - public static ServiceScope CreateScope(int order = 0) where TScope : IScope - => World.CreateScope(order); - - public static ServiceScope GetOrCreateScope(int order = 0) where TScope : IScope - => World.GetOrCreateScope(order); - - public static bool TryGetScope(out ServiceScope scope) where TScope : IScope + public static bool TryGetScene(out ServiceScope scope) { if (_world == null) { scope = null; return false; } - return _world.TryGetScope(out scope); + return _world.TryGetScene(out scope); } - public static bool DestroyScope() where TScope : IScope + public static ServiceScope ResetScene(int order = ServiceDomainOrder.Scene) + => World.ResetScene(order); + + public static bool DestroyScene() + => _world != null && _world.DestroyScene(); + + public static ServiceScope EnsureGameplay(int order = ServiceDomainOrder.Gameplay) + => World.EnsureGameplay(order); + + public static bool TryGetGameplay(out ServiceScope scope) { - if (_world == null) return false; - return _world.DestroyScope(); + if (_world == null) { scope = null; return false; } + return _world.TryGetGameplay(out scope); } - // ── Service 查找 ──────────────────────────────────────────────────────── + public static bool DestroyGameplay() + => _world != null && _world.DestroyGameplay(); public static bool TryGet(out T service) where T : class, IService { @@ -58,8 +69,6 @@ namespace AlicizaX public static T Require() where T : class, IService => World.Require(); - // ── 生命周期 ──────────────────────────────────────────────────────────── - public static void Shutdown() { if (_world == null) return; diff --git a/Runtime/ABase/Base/Service/Core/ServiceContext.cs b/Runtime/ABase/Base/Service/Core/ServiceContext.cs index 8c1e038..1a179a0 100644 --- a/Runtime/ABase/Base/Service/Core/ServiceContext.cs +++ b/Runtime/ABase/Base/Service/Core/ServiceContext.cs @@ -12,7 +12,17 @@ namespace AlicizaX public ServiceScope Scope { get; } - public ServiceScope AppScope => World.AppScope; + public ServiceScope App => World.App; + + public ServiceScope AppScope => App; + + public bool HasScene => World.HasScene; + + public bool HasGameplay => World.HasGameplay; + + public ServiceScope Scene => World.Scene; + + public ServiceScope Gameplay => World.Gameplay; public T Require() where T : class, IService => World.Require(Scope); @@ -20,13 +30,28 @@ namespace AlicizaX public bool TryGet(out T service) where T : class, IService => World.TryGet(Scope, out service); - public ServiceScope CreateScope(int order = 0) where TScope : IScope - => World.CreateScope(order); + public ServiceScope EnsureScene(int order = ServiceDomainOrder.Scene) + => World.EnsureScene(order); - public ServiceScope GetOrCreateScope(int order = 0) where TScope : IScope - => World.GetOrCreateScope(order); + public bool TryGetScene(out ServiceScope scope) + => World.TryGetScene(out scope); - public bool TryGetScope(out ServiceScope scope) where TScope : IScope - => World.TryGetScope(out scope); + public ServiceScope ResetScene(int order = ServiceDomainOrder.Scene) + => World.ResetScene(order); + + public ServiceScope EnsureGameplay(int order = ServiceDomainOrder.Gameplay) + => World.EnsureGameplay(order); + + public bool TryGetGameplay(out ServiceScope scope) + => World.TryGetGameplay(out scope); + + public T RequireApp() where T : class, IService + => App.Require(); + + public T RequireScene() where T : class, IService + => Scene.Require(); + + public T RequireGameplay() where T : class, IService + => Gameplay.Require(); } } diff --git a/Runtime/ABase/Base/Service/Core/ServiceWorld.cs b/Runtime/ABase/Base/Service/Core/ServiceWorld.cs index 0ad088f..8535df6 100644 --- a/Runtime/ABase/Base/Service/Core/ServiceWorld.cs +++ b/Runtime/ABase/Base/Service/Core/ServiceWorld.cs @@ -10,58 +10,89 @@ namespace AlicizaX private ServiceScope[] _scopeSnapshot = Array.Empty(); private bool _scopesDirty; + private ServiceScope _sceneScope; + private ServiceScope _gameplayScope; - public ServiceWorld(int appScopeOrder = -10000) + public ServiceWorld(int appScopeOrder = ServiceDomainOrder.App) { - AppScope = CreateScopeInternal(typeof(AppScope), appScopeOrder); + App = CreateScopeInternal(typeof(AppScope), nameof(AppScope), appScopeOrder); } - public ServiceScope AppScope { get; } + public ServiceScope App { get; } - // ── Scope 管理(Type-based) ──────────────────────────────────────────── + public ServiceScope AppScope => App; - public ServiceScope CreateScope(int order = 0) where TScope : IScope + public bool HasScene => _sceneScope != null && !_sceneScope.IsDisposed; + + public bool HasGameplay => _gameplayScope != null && !_gameplayScope.IsDisposed; + + public ServiceScope Scene { - var type = typeof(TScope); - if (_scopesByType.ContainsKey(type)) - throw new InvalidOperationException($"Scope {type.Name} already exists."); - return CreateScopeInternal(type, order); + get + { + if (!HasScene) + throw new InvalidOperationException("Scene scope has not been created yet."); + return _sceneScope; + } } - public ServiceScope GetOrCreateScope(int order = 0) where TScope : IScope + public ServiceScope Gameplay { - var type = typeof(TScope); - if (_scopesByType.TryGetValue(type, out var existing)) - return existing; - return CreateScopeInternal(type, order); + get + { + if (!HasGameplay) + throw new InvalidOperationException("Gameplay scope has not been created yet."); + return _gameplayScope; + } } - public bool TryGetScope(out ServiceScope scope) where TScope : IScope - => _scopesByType.TryGetValue(typeof(TScope), out scope); + public ServiceScope EnsureScene(int order = ServiceDomainOrder.Scene) + => _sceneScope is { IsDisposed: false } + ? _sceneScope + : _sceneScope = CreateScopeInternal(typeof(SceneScope), nameof(SceneScope), order); - public ServiceScope GetScope() where TScope : IScope + public bool TryGetScene(out ServiceScope scope) { - if (TryGetScope(out var scope)) return scope; - throw new InvalidOperationException($"Scope {typeof(TScope).Name} does not exist."); + scope = HasScene ? _sceneScope : null; + return scope != null; } - public bool DestroyScope() where TScope : IScope + public ServiceScope ResetScene(int order = ServiceDomainOrder.Scene) { - if (typeof(TScope) == typeof(AppScope)) - throw new InvalidOperationException("AppScope can only be destroyed when the world is disposed."); + DestroyScene(); + return EnsureScene(order); + } - var type = typeof(TScope); - if (!_scopesByType.TryGetValue(type, out var scope)) + public bool DestroyScene() + { + if (!HasScene) return false; - _scopesByType.Remove(type); - _scopes.Remove(scope); - _scopesDirty = true; - scope.Dispose(); - return true; + var scope = _sceneScope; + _sceneScope = null; + return DestroyScope(scope, typeof(SceneScope)); } - // ── Service 查找 ──────────────────────────────────────────────────────── + public ServiceScope EnsureGameplay(int order = ServiceDomainOrder.Gameplay) + => _gameplayScope is { IsDisposed: false } + ? _gameplayScope + : _gameplayScope = CreateScopeInternal(typeof(GameplayScope), nameof(GameplayScope), order); + + public bool TryGetGameplay(out ServiceScope scope) + { + scope = HasGameplay ? _gameplayScope : null; + return scope != null; + } + + public bool DestroyGameplay() + { + if (!HasGameplay) + return false; + + var scope = _gameplayScope; + _gameplayScope = null; + return DestroyScope(scope, typeof(GameplayScope)); + } public bool TryGet(out T service) where T : class, IService => TryGet(null, out service); @@ -91,8 +122,6 @@ namespace AlicizaX throw new InvalidOperationException($"Service {typeof(T).FullName} was not found in any active scope."); } - // ── Tick ──────────────────────────────────────────────────────────────── - public void Tick(float deltaTime) { var snapshot = GetScopeSnapshot(); @@ -117,23 +146,37 @@ namespace AlicizaX for (var i = 0; i < snapshot.Length; i++) snapshot[i].DrawGizmos(); } - // ── Dispose ───────────────────────────────────────────────────────────── - public void Dispose() { var snapshot = _scopes.ToArray(); for (var i = snapshot.Length - 1; i >= 0; i--) snapshot[i].Dispose(); + + _sceneScope = null; + _gameplayScope = null; _scopes.Clear(); _scopesByType.Clear(); _scopeSnapshot = Array.Empty(); } - // ── 内部 ──────────────────────────────────────────────────────────────── - - private ServiceScope CreateScopeInternal(Type scopeType, int order) + private bool DestroyScope(ServiceScope scope, Type scopeType) { - var scope = new ServiceScope(this, scopeType.Name, order); + if (scope == null) + return false; + + _scopesByType.Remove(scopeType); + _scopes.Remove(scope); + _scopesDirty = true; + scope.Dispose(); + return true; + } + + private ServiceScope CreateScopeInternal(Type scopeType, string scopeName, int order) + { + if (_scopesByType.ContainsKey(scopeType)) + throw new InvalidOperationException($"Scope {scopeType.Name} already exists."); + + var scope = new ServiceScope(this, scopeName, order); _scopes.Add(scope); _scopesByType.Add(scopeType, scope); _scopes.Sort(CompareScopeOrder); diff --git a/Runtime/ABase/Base/Service/Unity/AppServiceRoot.cs b/Runtime/ABase/Base/Service/Unity/AppServiceRoot.cs index 1f025e1..44e337e 100644 --- a/Runtime/ABase/Base/Service/Unity/AppServiceRoot.cs +++ b/Runtime/ABase/Base/Service/Unity/AppServiceRoot.cs @@ -10,7 +10,7 @@ namespace AlicizaX private static AppServiceRoot s_activeRoot; [SerializeField] private bool _dontDestroyOnLoad = true; - [SerializeField] private int _appScopeOrder = -10000; + [SerializeField] private int _appScopeOrder = ServiceDomainOrder.App; private bool _ownsWorld; @@ -32,7 +32,7 @@ namespace AlicizaX _ownsWorld = createdWorld; if (createdWorld) - RegisterAppServices(world.AppScope); + RegisterAppServices(world.App); } protected virtual void Update() diff --git a/Runtime/ABase/Base/Service/Unity/MonoServiceBehaviour.cs b/Runtime/ABase/Base/Service/Unity/MonoServiceBehaviour.cs index 0f8e7a0..69c6d02 100644 --- a/Runtime/ABase/Base/Service/Unity/MonoServiceBehaviour.cs +++ b/Runtime/ABase/Base/Service/Unity/MonoServiceBehaviour.cs @@ -2,9 +2,6 @@ using UnityEngine; namespace AlicizaX { - /// - /// Mono 服务基类(不自动注册,适合需要手动控制注册时机的场景)。 - /// public abstract class MonoServiceBehaviour : MonoBehaviour, IMonoService { public ServiceContext Context { get; private set; } @@ -39,25 +36,11 @@ namespace AlicizaX protected virtual void OnDestroyService() { } } - /// - /// Mono 服务基类(自动注册到 )。 - /// - /// 场景服务:_dontDestroyOnLoad = false(默认),销毁时自动注销。
- /// 跨场景服务:_dontDestroyOnLoad = true,首个实例持久化并注册; - /// 后续场景中出现的重复实例自动销毁自身。 - ///
- /// - /// 子类通过 执行额外的 Awake 逻辑, - /// 通过 执行注册后的初始化, - /// 通过 执行注销前的清理。 - /// - ///
public abstract class MonoServiceBehaviour : MonoServiceBehaviour where TScope : IScope { [SerializeField] private bool _dontDestroyOnLoad = false; - private void Awake() { OnAwake(); @@ -65,9 +48,8 @@ namespace AlicizaX private void Start() { - var scope = AppServices.GetOrCreateScope(); + var scope = ResolveOrCreateScope(); - // 跨场景重复实例检测:契约已被占用则销毁自身 if (scope.HasContract(GetType())) { Destroy(gameObject); @@ -84,15 +66,43 @@ namespace AlicizaX { if (!IsInitialized) return; if (!AppServices.HasWorld) return; - if (!AppServices.TryGetScope(out var scope)) return; + if (!TryResolveScope(out var scope)) return; scope.Unregister(this); } - /// - /// 在 Awake 阶段执行(早于 Start 中的自动注册)。 - /// 适合缓存组件引用等不依赖服务系统的初始化。 - /// + private static ServiceScope ResolveOrCreateScope() + { + if (typeof(TScope) == typeof(AppScope)) + return AppServices.App; + + if (typeof(TScope) == typeof(SceneScope)) + return AppServices.EnsureScene(); + + if (typeof(TScope) == typeof(GameplayScope)) + return AppServices.EnsureGameplay(); + + throw new System.InvalidOperationException($"Unsupported service scope: {typeof(TScope).FullName}."); + } + + private static bool TryResolveScope(out ServiceScope scope) + { + if (typeof(TScope) == typeof(AppScope)) + { + scope = AppServices.App; + return true; + } + + if (typeof(TScope) == typeof(SceneScope)) + return AppServices.TryGetScene(out scope); + + if (typeof(TScope) == typeof(GameplayScope)) + return AppServices.TryGetGameplay(out scope); + + scope = null; + return false; + } + protected virtual void OnAwake() { } } } diff --git a/Runtime/ABase/GameObjectPool/GameObjectPoolManager.cs b/Runtime/ABase/GameObjectPool/GameObjectPoolManager.cs index 29cd256..9606dcb 100644 --- a/Runtime/ABase/GameObjectPool/GameObjectPoolManager.cs +++ b/Runtime/ABase/GameObjectPool/GameObjectPoolManager.cs @@ -40,7 +40,8 @@ namespace AlicizaX private UniTask _initializeTask; private bool _initializationCompleted; private Exception _initializationException; - private float _lastCleanupTime; + private bool _cleanupScheduled; + private float _nextCleanupTime = float.MaxValue; private bool _isShuttingDown; @@ -51,11 +52,14 @@ namespace AlicizaX _shutdownTokenSource = new CancellationTokenSource(); EnsureDefaultResourceLoaders(); EnsurePoolContainer(); + enabled = false; + Application.lowMemory += OnLowMemory; _initializeTask = InitializeAsync(_shutdownTokenSource.Token); } protected override void OnDestroyService() { + Application.lowMemory -= OnLowMemory; _shutdownTokenSource?.Cancel(); ClearAllPools(); @@ -75,9 +79,9 @@ namespace AlicizaX { await UniTask.WaitUntil(() => YooAsset.YooAssets.Initialized, cancellationToken: cancellationToken); LoadConfigs(); - _lastCleanupTime = Time.time; _initializationCompleted = true; await PrewarmConfiguredPoolsAsync(cancellationToken); + ScheduleCleanup(); } catch (OperationCanceledException) { @@ -92,18 +96,19 @@ namespace AlicizaX private void Update() { - if (!IsReady) + if (!IsReady || !_cleanupScheduled) { + enabled = false; return; } - if (Time.time - _lastCleanupTime < checkInterval) + if (Time.time < _nextCleanupTime) { return; } PerformCleanup(); - _lastCleanupTime = Time.time; + ScheduleCleanup(); } public void SetResourceLoader(IResourceLoader resourceLoader) @@ -205,7 +210,7 @@ namespace AlicizaX } PerformCleanup(); - _lastCleanupTime = Time.time; + ScheduleCleanup(); } public void ClearAllPools() @@ -224,6 +229,9 @@ namespace AlicizaX _resolvedConfigCache.Clear(); _groupLoaderCache.Clear(); ReleaseDebugSnapshots(); + _cleanupScheduled = false; + _nextCleanupTime = float.MaxValue; + enabled = false; } public List GetDebugSnapshots() @@ -248,6 +256,7 @@ namespace AlicizaX } _ownersByObject[gameObject] = pool; + NotifyPoolStateChanged(); } internal void UnregisterOwnedObject(GameObject gameObject) @@ -258,6 +267,17 @@ namespace AlicizaX } _ownersByObject.Remove(gameObject); + NotifyPoolStateChanged(); + } + + internal void NotifyPoolStateChanged() + { + if (!IsReady) + { + return; + } + + ScheduleCleanup(); } private GameObject GetGameObjectInternal(string assetPath, string group, Transform parent) @@ -347,6 +367,7 @@ namespace AlicizaX _shutdownTokenSource != null ? _shutdownTokenSource.Token : default); _poolsByKey.Add(poolKey, pool); + ScheduleCleanup(); return pool; } @@ -359,6 +380,43 @@ namespace AlicizaX } } + private void ScheduleCleanup() + { + if (!IsReady) + { + _cleanupScheduled = false; + _nextCleanupTime = float.MaxValue; + enabled = false; + return; + } + + float nextDueTime = float.MaxValue; + float now = Time.time; + foreach (RuntimePrefabPool pool in _poolsByKey.Values) + { + float candidate = pool.GetNextCleanupTime(now, checkInterval); + if (candidate < nextDueTime) + { + nextDueTime = candidate; + } + } + + _nextCleanupTime = nextDueTime; + _cleanupScheduled = nextDueTime < float.MaxValue; + enabled = _cleanupScheduled; + } + + private void OnLowMemory() + { + if (!IsReady) + { + return; + } + + PerformCleanup(); + ScheduleCleanup(); + } + private void EnsureDefaultResourceLoaders() { if (!_resourceLoaders.ContainsKey(PoolResourceLoaderType.AssetBundle)) diff --git a/Runtime/ABase/GameObjectPool/IResourceLoader.cs b/Runtime/ABase/GameObjectPool/IResourceLoader.cs index f4b2888..e389cbc 100644 --- a/Runtime/ABase/GameObjectPool/IResourceLoader.cs +++ b/Runtime/ABase/GameObjectPool/IResourceLoader.cs @@ -16,18 +16,51 @@ namespace AlicizaX public class UnityResourcesLoader : IResourceLoader { + private static bool AllowLegacyResourcesFallback => Application.isEditor || Debug.isDebugBuild; + public GameObject LoadPrefab(string location) { - return Resources.Load(location); + if (TryGetResourceService(out var resourceService)) + { + var prefab = resourceService.LoadAsset(location); + if (prefab != null) + { + return prefab; + } + } + + return AllowLegacyResourcesFallback ? Resources.Load(location) : null; } public async UniTask LoadPrefabAsync(string location, CancellationToken cancellationToken = default) { - return await Resources.LoadAsync(location).ToUniTask(cancellationToken: cancellationToken) as GameObject; + if (TryGetResourceService(out var resourceService)) + { + var prefab = await resourceService.LoadAssetAsync(location, cancellationToken); + if (prefab != null) + { + return prefab; + } + } + + return AllowLegacyResourcesFallback + ? await Resources.LoadAsync(location).ToUniTask(cancellationToken: cancellationToken) as GameObject + : null; } public GameObject LoadGameObject(string location, Transform parent = null) { + if (TryGetResourceService(out var resourceService)) + { + var managedObject = resourceService.LoadGameObject(location, parent); + if (managedObject != null) + { + return managedObject; + } + } + + if (!AllowLegacyResourcesFallback) return null; + var prefab = Resources.Load(location); if (prefab == null) return null; @@ -42,6 +75,17 @@ namespace AlicizaX public async UniTask LoadGameObjectAsync(string location, Transform parent = null, CancellationToken cancellationToken = default) { + if (TryGetResourceService(out var resourceService)) + { + var managedObject = await resourceService.LoadGameObjectAsync(location, parent, cancellationToken); + if (managedObject != null) + { + return managedObject; + } + } + + if (!AllowLegacyResourcesFallback) return null; + var prefab = await Resources.LoadAsync(location).ToUniTask(cancellationToken: cancellationToken) as GameObject; if (prefab == null) return null; @@ -59,6 +103,11 @@ namespace AlicizaX // Resources.UnloadAsset cannot unload GameObjects. // The prefab reference is nulled by the caller; Unity handles cleanup via UnloadUnusedAssets. } + + private static bool TryGetResourceService(out IResourceService resourceService) + { + return AppServices.TryGet(out resourceService); + } } public class AssetBundleResourceLoader : IResourceLoader diff --git a/Runtime/ABase/GameObjectPool/RuntimePoolModels.cs b/Runtime/ABase/GameObjectPool/RuntimePoolModels.cs index 47859a6..708d534 100644 --- a/Runtime/ABase/GameObjectPool/RuntimePoolModels.cs +++ b/Runtime/ABase/GameObjectPool/RuntimePoolModels.cs @@ -241,6 +241,7 @@ namespace AlicizaX instance.InactiveNode = _inactiveInstances.AddLast(instance); TouchPrefab(); + _service.NotifyPoolStateChanged(); return true; } @@ -281,6 +282,34 @@ namespace AlicizaX } } + public float GetNextCleanupTime(float now, float fallbackDelay) + { + float nextDueTime = float.MaxValue; + + if (_config.instanceIdleTimeout > 0f && _inactiveInstances.First != null) + { + float instanceDueTime = _inactiveInstances.First.Value.LastReleaseTime + _config.instanceIdleTimeout; + nextDueTime = Math.Min(nextDueTime, Math.Max(now, instanceDueTime)); + } + + if (_prefab != null && + _instancesByObject.Count == 0 && + _config.prefabUnloadDelay > 0f) + { + float prefabDueTime = _lastPrefabTouchTime + _config.prefabUnloadDelay; + nextDueTime = Math.Min(nextDueTime, Math.Max(now, prefabDueTime)); + } + + if (nextDueTime < float.MaxValue) + { + return nextDueTime; + } + + return _instancesByObject.Count > 0 || _prefab != null + ? now + Math.Max(fallbackDelay, 0.1f) + : float.MaxValue; + } + public GameObjectPoolSnapshot CreateSnapshot() { var snapshot = MemoryPool.Acquire(); @@ -489,6 +518,7 @@ namespace AlicizaX transform.localScale = Vector3.one; instance.GameObject.SetActive(false); instance.InactiveNode = _inactiveInstances.AddLast(instance); + _service.NotifyPoolStateChanged(); } private void DestroyInstance(RuntimePooledInstance instance) @@ -528,6 +558,7 @@ namespace AlicizaX _instancesByObject.Remove(gameObject); _service.UnregisterOwnedObject(gameObject); TouchPrefab(); + _service.NotifyPoolStateChanged(); } private void RemoveInactiveNode(RuntimePooledInstance instance) diff --git a/Runtime/ABase/ModuleDynamicBind.cs b/Runtime/ABase/ModuleDynamicBind.cs index d6f267a..1e89617 100644 --- a/Runtime/ABase/ModuleDynamicBind.cs +++ b/Runtime/ABase/ModuleDynamicBind.cs @@ -9,9 +9,9 @@ namespace AlicizaX { public class ModuleDynamicBind : MonoBehaviour { - [FormerlySerializedAs("_resourceServiceBehaviour")] [SerializeField] private ResourceComponent resourceComponent; - [FormerlySerializedAs("_debuggerServiceBehaviour")] [SerializeField] private DebuggerComponent debuggerComponent; - [FormerlySerializedAs("_localizationServiceBehaviour")] [SerializeField] private LocalizationComponent localizationComponent; + [SerializeField] private ResourceComponent resourceComponent; + [SerializeField] private DebuggerComponent debuggerComponent; + [SerializeField] private LocalizationComponent localizationComponent; private ModuleDynamicBindInfo _dynamicBindInfo; private void OnValidate() @@ -24,7 +24,23 @@ namespace AlicizaX private void Awake() { if (Application.isEditor) return; - TextAsset text = Resources.Load("ModuleDynamicBindInfo"); + TextAsset text = null; + if (AppServices.TryGet(out var resourceService)) + { + text = resourceService.LoadAsset("ModuleDynamicBindInfo"); + } + + if (text == null) + { + text = Resources.Load("ModuleDynamicBindInfo"); + } + + if (text == null) + { + Log.Warning("ModuleDynamicBindInfo not found."); + return; + } + _dynamicBindInfo = Utility.Json.ToObject(text.text); if (resourceComponent != null) diff --git a/Runtime/ABase/ObjectPool/IObjectPoolService.cs b/Runtime/ABase/ObjectPool/IObjectPoolService.cs index 369981a..e3f242b 100644 --- a/Runtime/ABase/ObjectPool/IObjectPoolService.cs +++ b/Runtime/ABase/ObjectPool/IObjectPoolService.cs @@ -1,745 +1,80 @@ -using System; +using System; using System.Collections.Generic; using AlicizaX; namespace AlicizaX.ObjectPool { + public readonly struct ObjectPoolCreateOptions + { + public readonly string Name; + public readonly bool AllowMultiSpawn; + public readonly float AutoReleaseInterval; + public readonly int Capacity; + public readonly float ExpireTime; + public readonly int Priority; + + public ObjectPoolCreateOptions( + string name = "", + bool allowMultiSpawn = false, + float autoReleaseInterval = float.MaxValue, + int capacity = int.MaxValue, + float expireTime = float.MaxValue, + int priority = 0) + { + Name = name ?? string.Empty; + AllowMultiSpawn = allowMultiSpawn; + AutoReleaseInterval = autoReleaseInterval; + Capacity = capacity; + ExpireTime = expireTime; + Priority = priority; + } + + public ObjectPoolCreateOptions WithName(string name) + => new ObjectPoolCreateOptions(name, AllowMultiSpawn, AutoReleaseInterval, Capacity, ExpireTime, Priority); + + public static ObjectPoolCreateOptions Single(string name = "") + => new ObjectPoolCreateOptions(name: name); + + public static ObjectPoolCreateOptions Multi(string name = "") + => new ObjectPoolCreateOptions(name: name, allowMultiSpawn: true); + } + /// /// 对象池管理器。 /// public interface IObjectPoolService : IService, IServiceTickable { - /// - /// 获取对象池数量。 - /// - int Count - { - get; - } + int Count { get; } - /// - /// 检查是否存在对象池。 - /// - /// 对象类型。 - /// 是否存在对象池。 bool HasObjectPool() where T : ObjectBase; - - /// - /// 检查是否存在对象池。 - /// - /// 对象类型。 - /// 是否存在对象池。 bool HasObjectPool(Type objectType); - - /// - /// 检查是否存在对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 是否存在对象池。 bool HasObjectPool(string name) where T : ObjectBase; - - /// - /// 检查是否存在对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 是否存在对象池。 bool HasObjectPool(Type objectType, string name); - - /// - /// 检查是否存在对象池。 - /// - /// 要检查的条件。 - /// 是否存在对象池。 bool HasObjectPool(Predicate condition); - /// - /// 获取对象池。 - /// - /// 对象类型。 - /// 要获取的对象池。 IObjectPool GetObjectPool() where T : ObjectBase; - - /// - /// 获取对象池。 - /// - /// 对象类型。 - /// 要获取的对象池。 ObjectPoolBase GetObjectPool(Type objectType); - - /// - /// 获取对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 要获取的对象池。 IObjectPool GetObjectPool(string name) where T : ObjectBase; - - /// - /// 获取对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 要获取的对象池。 ObjectPoolBase GetObjectPool(Type objectType, string name); - - /// - /// 获取对象池。 - /// - /// 要检查的条件。 - /// 要获取的对象池。 ObjectPoolBase GetObjectPool(Predicate condition); - - /// - /// 获取对象池。 - /// - /// 要检查的条件。 - /// 要获取的对象池。 ObjectPoolBase[] GetObjectPools(Predicate condition); - - /// - /// 获取对象池。 - /// - /// 要检查的条件。 - /// 要获取的对象池。 void GetObjectPools(Predicate condition, List results); - - /// - /// 获取所有对象池。 - /// - /// 所有对象池。 ObjectPoolBase[] GetAllObjectPools(); - - /// - /// 获取所有对象池。 - /// - /// 所有对象池。 void GetAllObjectPools(List results); - - /// - /// 获取所有对象池。 - /// - /// 是否根据对象池的优先级排序。 - /// 所有对象池。 ObjectPoolBase[] GetAllObjectPools(bool sort); - - /// - /// 获取所有对象池。 - /// - /// 是否根据对象池的优先级排序。 - /// 所有对象池。 void GetAllObjectPools(bool sort, List results); - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool() where T : ObjectBase; + IObjectPool CreatePool(ObjectPoolCreateOptions options = default) where T : ObjectBase; + ObjectPoolBase CreatePool(Type objectType, ObjectPoolCreateOptions options = default); - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(string name) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(int capacity) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池对象过期秒数。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(float expireTime) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池对象过期秒数。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, float expireTime); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(string name, int capacity) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池对象过期秒数。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(string name, float expireTime) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池对象过期秒数。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, float expireTime); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(int capacity, float expireTime) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity, float expireTime); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(int capacity, int priority) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity, int priority); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, float expireTime, int priority); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(string name, int capacity, float expireTime) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity, float expireTime); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(string name, int capacity, int priority) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity, int priority); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(string name, float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, float expireTime, int priority); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(int capacity, float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, int capacity, float expireTime, int priority); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(string name, int capacity, float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, int capacity, float expireTime, int priority); - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池自动释放可释放对象的间隔秒数。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - IObjectPool CreateSingleSpawnObjectPool(string name, float autoReleaseInterval, int capacity, float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许单次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池自动释放可释放对象的间隔秒数。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许单次获取的对象池。 - ObjectPoolBase CreateSingleSpawnObjectPool(Type objectType, string name, float autoReleaseInterval, int capacity, float expireTime, int priority); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool() where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(string name) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(int capacity) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池对象过期秒数。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(float expireTime) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池对象过期秒数。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, float expireTime); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(string name, int capacity) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池对象过期秒数。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(string name, float expireTime) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池对象过期秒数。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, float expireTime); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(int capacity, float expireTime) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity, float expireTime); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(int capacity, int priority) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity, int priority); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, float expireTime, int priority); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(string name, int capacity, float expireTime) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity, float expireTime); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(string name, int capacity, int priority) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity, int priority); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(string name, float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, float expireTime, int priority); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(int capacity, float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, int capacity, float expireTime, int priority); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(string name, int capacity, float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, int capacity, float expireTime, int priority); - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池自动释放可释放对象的间隔秒数。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - IObjectPool CreateMultiSpawnObjectPool(string name, float autoReleaseInterval, int capacity, float expireTime, int priority) where T : ObjectBase; - - /// - /// 创建允许多次获取的对象池。 - /// - /// 对象类型。 - /// 对象池名称。 - /// 对象池自动释放可释放对象的间隔秒数。 - /// 对象池的容量。 - /// 对象池对象过期秒数。 - /// 对象池的优先级。 - /// 要创建的允许多次获取的对象池。 - ObjectPoolBase CreateMultiSpawnObjectPool(Type objectType, string name, float autoReleaseInterval, int capacity, float expireTime, int priority); - - /// - /// 销毁对象池。 - /// - /// 对象类型。 - /// 是否销毁对象池成功。 bool DestroyObjectPool() where T : ObjectBase; - - /// - /// 销毁对象池。 - /// - /// 对象类型。 - /// 是否销毁对象池成功。 bool DestroyObjectPool(Type objectType); - - /// - /// 销毁对象池。 - /// - /// 对象类型。 - /// 要销毁的对象池名称。 - /// 是否销毁对象池成功。 bool DestroyObjectPool(string name) where T : ObjectBase; - - /// - /// 销毁对象池。 - /// - /// 对象类型。 - /// 要销毁的对象池名称。 - /// 是否销毁对象池成功。 bool DestroyObjectPool(Type objectType, string name); - - /// - /// 销毁对象池。 - /// - /// 对象类型。 - /// 要销毁的对象池。 - /// 是否销毁对象池成功。 bool DestroyObjectPool(IObjectPool objectPool) where T : ObjectBase; - - /// - /// 销毁对象池。 - /// - /// 要销毁的对象池。 - /// 是否销毁对象池成功。 bool DestroyObjectPool(ObjectPoolBase objectPool); - /// - /// 释放对象池中的可释放对象。 - /// void Release(); - - /// - /// 释放对象池中的所有未使用对象。 - /// void ReleaseAllUnused(); } } diff --git a/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs b/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs index c0978d7..f112ae5 100644 --- a/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs +++ b/Runtime/ABase/ObjectPool/ObjectPoolComponent.cs @@ -1,17 +1,17 @@ -using AlicizaX.ObjectPool; +using AlicizaX.ObjectPool; using UnityEngine; namespace AlicizaX { /// - /// 对象池组件。 + /// /// public sealed class ObjectPoolComponent : MonoBehaviour { private IObjectPoolService _mObjectPoolService = null; /// - /// 获取对象池数量。 + /// ȡ /// public int Count { @@ -20,7 +20,7 @@ namespace AlicizaX private void Awake() { - _mObjectPoolService = AppServices.GetOrCreateScope().Register(new ObjectPoolService()); + _mObjectPoolService = AppServices.App.Register(new ObjectPoolService()); } private void OnDestroy() @@ -29,13 +29,14 @@ namespace AlicizaX } /// - /// 获取所有对象池。 + /// ȡжء /// - /// 是否根据对象池的优先级排序。 - /// 所有对象池。 + /// Ƿݶصȼ + /// жء public ObjectPoolBase[] GetAllObjectPools(bool sort) { return _mObjectPoolService.GetAllObjectPools(sort); } } } + diff --git a/Runtime/ABase/ObjectPool/ObjectPoolService.cs b/Runtime/ABase/ObjectPool/ObjectPoolService.cs index 0d52bfa..4d61776 100644 --- a/Runtime/ABase/ObjectPool/ObjectPoolService.cs +++ b/Runtime/ABase/ObjectPool/ObjectPoolService.cs @@ -354,6 +354,29 @@ namespace AlicizaX.ObjectPool } } + public IObjectPool CreatePool(ObjectPoolCreateOptions options = default) where T : ObjectBase + { + return InternalCreateObjectPool( + options.Name, + options.AllowMultiSpawn, + NormalizeAutoReleaseInterval(options.AutoReleaseInterval), + NormalizeCapacity(options.Capacity), + NormalizeExpireTime(options.ExpireTime), + options.Priority); + } + + public ObjectPoolBase CreatePool(Type objectType, ObjectPoolCreateOptions options = default) + { + return InternalCreateObjectPool( + objectType, + options.Name, + options.AllowMultiSpawn, + NormalizeAutoReleaseInterval(options.AutoReleaseInterval), + NormalizeCapacity(options.Capacity), + NormalizeExpireTime(options.ExpireTime), + options.Priority); + } + /// /// 创建允许单次获取的对象池。 /// @@ -1212,6 +1235,21 @@ namespace AlicizaX.ObjectPool return m_ObjectPools.ContainsKey(typeNamePair); } + private static float NormalizeAutoReleaseInterval(float autoReleaseInterval) + { + return autoReleaseInterval == default ? DefaultExpireTime : autoReleaseInterval; + } + + private static int NormalizeCapacity(int capacity) + { + return capacity == default ? DefaultCapacity : capacity; + } + + private static float NormalizeExpireTime(float expireTime) + { + return expireTime == default ? DefaultExpireTime : expireTime; + } + private ObjectPoolBase InternalGetObjectPool(TypeNamePair typeNamePair) { ObjectPoolBase objectPool = null; diff --git a/Runtime/ABase/Utility/Utility.Unity.cs b/Runtime/ABase/Utility/Utility.Unity.cs index 5de3720..8a1a5dd 100644 --- a/Runtime/ABase/Utility/Utility.Unity.cs +++ b/Runtime/ABase/Utility/Utility.Unity.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.Events; using UnityEngine.Internal; @@ -17,6 +16,7 @@ namespace AlicizaX { private static GameObject _entity; private static MainBehaviour _behaviour; + private static UtilityLoopService _loopService; #region 控制协程Coroutine @@ -103,13 +103,7 @@ namespace AlicizaX /// public static void AddUpdateListener(UnityAction fun) { - AddUpdateListenerImp(fun).Forget(); - } - - private static async UniTaskVoid AddUpdateListenerImp(UnityAction fun) - { - await UniTask.Yield(PlayerLoopTiming.LastPreUpdate); - _behaviour.AddUpdateListener(fun); + EnsureLoopService().AddUpdateListener(fun); } /// @@ -118,13 +112,7 @@ namespace AlicizaX /// public static void AddFixedUpdateListener(UnityAction fun) { - AddFixedUpdateListenerImp(fun).Forget(); - } - - private static async UniTaskVoid AddFixedUpdateListenerImp(UnityAction fun) - { - await UniTask.Yield(PlayerLoopTiming.LastEarlyUpdate); - _behaviour.AddFixedUpdateListener(fun); + EnsureLoopService().AddFixedUpdateListener(fun); } /// @@ -133,13 +121,7 @@ namespace AlicizaX /// public static void AddLateUpdateListener(UnityAction fun) { - AddLateUpdateListenerImp(fun).Forget(); - } - - private static async UniTaskVoid AddLateUpdateListenerImp(UnityAction fun) - { - await UniTask.Yield(PlayerLoopTiming.LastPreLateUpdate); - _behaviour.AddLateUpdateListener(fun); + EnsureLoopService().AddLateUpdateListener(fun); } /// @@ -148,7 +130,7 @@ namespace AlicizaX /// public static void RemoveUpdateListener(UnityAction fun) { - _behaviour.RemoveUpdateListener(fun); + _loopService?.RemoveUpdateListener(fun); } /// @@ -157,7 +139,7 @@ namespace AlicizaX /// public static void RemoveFixedUpdateListener(UnityAction fun) { - _behaviour.RemoveFixedUpdateListener(fun); + _loopService?.RemoveFixedUpdateListener(fun); } /// @@ -166,7 +148,7 @@ namespace AlicizaX /// public static void RemoveLateUpdateListener(UnityAction fun) { - _behaviour.RemoveLateUpdateListener(fun); + _loopService?.RemoveLateUpdateListener(fun); } #endregion @@ -196,7 +178,7 @@ namespace AlicizaX /// public static void AddOnDrawGizmosListener(UnityAction fun) { - _behaviour.AddOnDrawGizmosListener(fun); + EnsureLoopService().AddOnDrawGizmosListener(fun); } /// @@ -205,7 +187,7 @@ namespace AlicizaX /// public static void RemoveOnDrawGizmosListener(UnityAction fun) { - _behaviour.RemoveOnDrawGizmosListener(fun); + _loopService?.RemoveOnDrawGizmosListener(fun); } /// @@ -256,6 +238,7 @@ namespace AlicizaX _entity.SetActive(true); _entity.transform.SetParent(parent); _behaviour = _entity.AddComponent(); + RegisterLoopServiceIfNeeded(); } /// @@ -270,6 +253,12 @@ namespace AlicizaX _behaviour.Shutdown(); } + if (_loopService != null) + { + _loopService.Clear(); + _loopService = null; + } + if (_entity != null) { UnityEngine.Object.Destroy(_entity); @@ -279,40 +268,44 @@ namespace AlicizaX _entity = null; } + private static void RegisterLoopServiceIfNeeded() + { + if (_loopService != null) + { + return; + } + + if (!AppServices.HasWorld) + { + return; + } + + if (AppServices.TryGet(out UtilityLoopService service)) + { + _loopService = service; + return; + } + + _loopService = AppServices.App.Register(new UtilityLoopService()); + } + + private static UtilityLoopService EnsureLoopService() + { + RegisterLoopServiceIfNeeded(); + if (_loopService == null) + { + throw new InvalidOperationException("Utility loop service is not available."); + } + + return _loopService; + } + private class MainBehaviour : MonoBehaviour { - private event UnityAction UpdateEvent; - private event UnityAction FixedUpdateEvent; - private event UnityAction LateUpdateEvent; private event UnityAction DestroyEvent; - private event UnityAction OnDrawGizmosEvent; private event UnityAction OnApplicationPauseEvent; private event UnityAction OnApplicationQuitEvent; - void Update() - { - if (UpdateEvent != null) - { - UpdateEvent(); - } - } - - void FixedUpdate() - { - if (FixedUpdateEvent != null) - { - FixedUpdateEvent(); - } - } - - void LateUpdate() - { - if (LateUpdateEvent != null) - { - LateUpdateEvent(); - } - } - private void OnDestroy() { if (DestroyEvent != null) @@ -321,14 +314,6 @@ namespace AlicizaX } } - private void OnDrawGizmos() - { - if (OnDrawGizmosEvent != null) - { - OnDrawGizmosEvent(); - } - } - private void OnApplicationPause(bool pauseStatus) { if (OnApplicationPauseEvent != null) @@ -345,36 +330,6 @@ namespace AlicizaX } } - public void AddLateUpdateListener(UnityAction fun) - { - LateUpdateEvent += fun; - } - - public void RemoveLateUpdateListener(UnityAction fun) - { - LateUpdateEvent -= fun; - } - - public void AddFixedUpdateListener(UnityAction fun) - { - FixedUpdateEvent += fun; - } - - public void RemoveFixedUpdateListener(UnityAction fun) - { - FixedUpdateEvent -= fun; - } - - public void AddUpdateListener(UnityAction fun) - { - UpdateEvent += fun; - } - - public void RemoveUpdateListener(UnityAction fun) - { - UpdateEvent -= fun; - } - public void AddDestroyListener(UnityAction fun) { DestroyEvent += fun; @@ -385,16 +340,6 @@ namespace AlicizaX DestroyEvent -= fun; } - public void AddOnDrawGizmosListener(UnityAction fun) - { - OnDrawGizmosEvent += fun; - } - - public void RemoveOnDrawGizmosListener(UnityAction fun) - { - OnDrawGizmosEvent -= fun; - } - public void AddOnApplicationPauseListener(UnityAction fun) { OnApplicationPauseEvent += fun; @@ -418,10 +363,6 @@ namespace AlicizaX public void Shutdown() { - UpdateEvent = null; - FixedUpdateEvent = null; - LateUpdateEvent = null; - OnDrawGizmosEvent = null; DestroyEvent = null; OnApplicationPauseEvent = null; OnApplicationQuitEvent = null; @@ -439,6 +380,91 @@ namespace AlicizaX } } } + + private sealed class UtilityLoopService : ServiceBase, IServiceTickable, IServiceLateTickable, IServiceFixedTickable, IServiceGizmoDrawable + { + private event UnityAction UpdateEvent; + private event UnityAction FixedUpdateEvent; + private event UnityAction LateUpdateEvent; + private event UnityAction OnDrawGizmosEvent; + + protected override void OnInitialize() + { + } + + protected override void OnDestroyService() + { + Clear(); + } + + void IServiceTickable.Tick(float deltaTime) + { + UpdateEvent?.Invoke(); + } + + void IServiceLateTickable.LateTick(float deltaTime) + { + LateUpdateEvent?.Invoke(); + } + + void IServiceFixedTickable.FixedTick(float fixedDeltaTime) + { + FixedUpdateEvent?.Invoke(); + } + + void IServiceGizmoDrawable.DrawGizmos() + { + OnDrawGizmosEvent?.Invoke(); + } + + public void AddUpdateListener(UnityAction fun) + { + UpdateEvent += fun; + } + + public void RemoveUpdateListener(UnityAction fun) + { + UpdateEvent -= fun; + } + + public void AddFixedUpdateListener(UnityAction fun) + { + FixedUpdateEvent += fun; + } + + public void RemoveFixedUpdateListener(UnityAction fun) + { + FixedUpdateEvent -= fun; + } + + public void AddLateUpdateListener(UnityAction fun) + { + LateUpdateEvent += fun; + } + + public void RemoveLateUpdateListener(UnityAction fun) + { + LateUpdateEvent -= fun; + } + + public void AddOnDrawGizmosListener(UnityAction fun) + { + OnDrawGizmosEvent += fun; + } + + public void RemoveOnDrawGizmosListener(UnityAction fun) + { + OnDrawGizmosEvent -= fun; + } + + public void Clear() + { + UpdateEvent = null; + FixedUpdateEvent = null; + LateUpdateEvent = null; + OnDrawGizmosEvent = null; + } + } } } } diff --git a/Runtime/Audio/AudioComponent.cs b/Runtime/Audio/AudioComponent.cs index f11741c..ec621f0 100644 --- a/Runtime/Audio/AudioComponent.cs +++ b/Runtime/Audio/AudioComponent.cs @@ -1,13 +1,13 @@ -using AlicizaX; +using AlicizaX; using UnityEngine; using UnityEngine.Audio; namespace AlicizaX.Audio.Runtime { /// - /// 音效管理,为游戏提供统一的音效播放接口。 + /// ЧΪϷṩͳһЧŽӿڡ /// - /// 场景3D音效挂到场景物件、技能3D音效挂到技能特效上,并在AudioSource的Output上设置对应分类的AudioMixerGroup + /// 3DЧҵ3DЧҵЧϣAudioSourceOutputöӦAudioMixerGroup [DisallowMultipleComponent] [AddComponentMenu("Game Framework/Audio")] public sealed class AudioComponent : MonoBehaviour @@ -22,11 +22,11 @@ namespace AlicizaX.Audio.Runtime private void Awake() { - _audioService = AppServices.GetOrCreateScope().Register(new AudioService()); + _audioService = AppServices.App.Register(new AudioService()); } /// - /// 初始化音频模块。 + /// ʼƵģ顣 /// void Start() { @@ -46,3 +46,4 @@ namespace AlicizaX.Audio.Runtime } } } + diff --git a/Runtime/Debugger/DebuggerComponent.cs b/Runtime/Debugger/DebuggerComponent.cs index b1ee856..98517c7 100644 --- a/Runtime/Debugger/DebuggerComponent.cs +++ b/Runtime/Debugger/DebuggerComponent.cs @@ -6,7 +6,7 @@ using Object = UnityEngine.Object; namespace AlicizaX.Debugger.Runtime { /// - /// 调试器组件。 + /// 调试器组件? /// [DisallowMultipleComponent] [AddComponentMenu("Game Framework/Debugger")] @@ -28,17 +28,17 @@ namespace AlicizaX.Debugger.Runtime } /// - /// 默认调试器漂浮框大小。 + /// 默认调试器漂浮框大小? /// internal static readonly Rect DefaultIconRect = new Rect(10f, 10f, 60f, 60f); /// - /// 默认调试器窗口大小。 + /// 默认调试器窗口大小? /// internal static readonly Rect DefaultWindowRect = new Rect(10f, 10f, 640f, 480f); /// - /// 默认调试器窗口缩放比例。 + /// 默认调试器窗口缩放比例? /// internal static readonly float DefaultWindowScale = 1f; @@ -95,7 +95,7 @@ namespace AlicizaX.Debugger.Runtime private FpsCounter m_FpsCounter = null; /// - /// 获取或设置调试器窗口是否激活。 + /// 获取或设置调试器窗口是否激活? /// public bool ActiveWindow { @@ -108,7 +108,7 @@ namespace AlicizaX.Debugger.Runtime } /// - /// 获取或设置是否显示完整调试器界面。 + /// 获取或设置是否显示完整调试器界面? /// public bool ShowFullWindow { @@ -117,7 +117,7 @@ namespace AlicizaX.Debugger.Runtime } /// - /// 获取或设置调试器漂浮框大小。 + /// 获取或设置调试器漂浮框大小? /// public Rect IconRect { @@ -126,7 +126,7 @@ namespace AlicizaX.Debugger.Runtime } /// - /// 获取或设置调试器窗口大小。 + /// 获取或设置调试器窗口大小? /// public Rect WindowRect { @@ -135,7 +135,7 @@ namespace AlicizaX.Debugger.Runtime } /// - /// 获取或设置调试器窗口缩放比例。 + /// 获取或设置调试器窗口缩放比例? /// public float WindowScale { @@ -144,12 +144,12 @@ namespace AlicizaX.Debugger.Runtime } /// - /// 游戏框架组件初始化。 + /// 游戏框架组件初始化? /// private void Awake() { _instance = this; - _mDebuggerService = AppServices.GetOrCreateScope().Register(new DebuggerService()); + _mDebuggerService = AppServices.App.Register(new DebuggerService()); if (_mDebuggerService == null) { Log.Error("Debugger manager is invalid."); @@ -256,48 +256,48 @@ namespace AlicizaX.Debugger.Runtime } /// - /// 注册调试器窗口。 + /// 注册调试器窗口? /// - /// 调试器窗口路径。 - /// 要注册的调试器窗口。 - /// 初始化调试器窗口参数。 + /// 调试器窗口路径?/param> + /// 要注册的调试器窗口?/param> + /// 初始化调试器窗口参数?/param> public void RegisterDebuggerWindow(string path, IDebuggerWindow debuggerWindow, params object[] args) { _mDebuggerService.RegisterDebuggerWindow(path, debuggerWindow, args); } /// - /// 解除注册调试器窗口。 + /// 解除注册调试器窗口? /// - /// 调试器窗口路径。 - /// 是否解除注册调试器窗口成功。 + /// 调试器窗口路径?/param> + /// 是否解除注册调试器窗口成功?/returns> public bool UnregisterDebuggerWindow(string path) { return _mDebuggerService.UnregisterDebuggerWindow(path); } /// - /// 获取调试器窗口。 + /// 获取调试器窗口? /// - /// 调试器窗口路径。 - /// 要获取的调试器窗口。 + /// 调试器窗口路径?/param> + /// 要获取的调试器窗口?/returns> public IDebuggerWindow GetDebuggerWindow(string path) { return _mDebuggerService.GetDebuggerWindow(path); } /// - /// 选中调试器窗口。 + /// 选中调试器窗口? /// - /// 调试器窗口路径。 - /// 是否成功选中调试器窗口。 + /// 调试器窗口路径?/param> + /// 是否成功选中调试器窗口?/returns> public bool SelectDebuggerWindow(string path) { return _mDebuggerService.SelectDebuggerWindow(path); } /// - /// 还原调试器窗口布局。 + /// 还原调试器窗口布局? /// public void ResetLayout() { @@ -307,19 +307,19 @@ namespace AlicizaX.Debugger.Runtime } /// - /// 获取记录的所有日志。 + /// 获取记录的所有日志? /// - /// 要获取的日志。 + /// 要获取的日志?/param> public void GetRecentLogs(List results) { m_ConsoleWindow.GetRecentLogs(results); } /// - /// 获取记录的最近日志。 + /// 获取记录的最近日志? /// - /// 要获取的日志。 - /// 要获取最近日志的数量。 + /// 要获取的日志?/param> + /// 要获取最近日志的数量?/param> public void GetRecentLogs(List results, int count) { m_ConsoleWindow.GetRecentLogs(results, count); @@ -417,3 +417,4 @@ namespace AlicizaX.Debugger.Runtime } } } + diff --git a/Runtime/Localization/LocalizationComponent.cs b/Runtime/Localization/LocalizationComponent.cs index d460950..e839f30 100644 --- a/Runtime/Localization/LocalizationComponent.cs +++ b/Runtime/Localization/LocalizationComponent.cs @@ -1,36 +1,152 @@ -using UnityEngine; +using System.Collections.Generic; +using Cysharp.Threading.Tasks; +using AlicizaX.Resource.Runtime; +using UnityEngine; namespace AlicizaX.Localization.Runtime { /// - /// 本地化组件。 + /// ػ /// [DisallowMultipleComponent] [AddComponentMenu("Game Framework/Localization")] public sealed class LocalizationComponent : MonoBehaviour { + private const string DefaultLanguage = "ChineseSimplified"; + private const string RuntimeLanguagePrefsKey = "AlicizaX.Localization.Language"; + private ILocalizationService _mLocalizationService = null; public static string PrefsKey = Application.dataPath.GetHashCode() + "Language"; [SerializeField] private string _language; + [SerializeField] private List _startupTables = new(); + [SerializeField] private List _startupTableLocations = new(); + [SerializeField] private string _resourcePackageName = string.Empty; internal void SetLanguage(string language) { _language = language; } - private void Start() + internal static void SaveLanguagePreference(string language) { - _mLocalizationService = AppServices.GetOrCreateScope().Register(new LocalizationService()); + if (string.IsNullOrEmpty(language)) + { + return; + } + +#if UNITY_EDITOR + UnityEditor.EditorPrefs.SetString(PrefsKey, language); +#endif + PlayerPrefs.SetString(RuntimeLanguagePrefsKey, language); + PlayerPrefs.Save(); + } + + private static string LoadLanguagePreference(string fallbackLanguage) + { + string fallback = string.IsNullOrEmpty(fallbackLanguage) ? DefaultLanguage : fallbackLanguage; + string language = PlayerPrefs.GetString(RuntimeLanguagePrefsKey, fallback); +#if UNITY_EDITOR + language = UnityEditor.EditorPrefs.GetString(PrefsKey, language); +#endif + return string.IsNullOrEmpty(language) ? fallback : language; + } + + private void Awake() + { + if (!AppServices.App.TryGet(out _mLocalizationService)) + { + _mLocalizationService = AppServices.App.Register(new LocalizationService()); + } + if (_mLocalizationService == null) { Log.Info("Localization manager is invalid."); + return; } -#if UNITY_EDITOR - _language = UnityEditor.EditorPrefs.GetString(LocalizationComponent.PrefsKey, "None"); -#endif + + _language = LoadLanguagePreference(_language); _mLocalizationService.Initialize(_language); + ApplyStartupTables(_startupTables); + + if (_startupTableLocations != null && _startupTableLocations.Count > 0) + { + LoadStartupTablesAsync().Forget(); + } + } + + private void ApplyStartupTables(List tables) + { + if (tables == null || tables.Count == 0) + { + return; + } + + bool applied = false; + for (int i = 0; i < tables.Count; i++) + { + GameLocaizationTable table = tables[i]; + if (table == null) + { + continue; + } + + if (!applied) + { + _mLocalizationService.CoverAddLocalizationConfig(table); + applied = true; + continue; + } + + _mLocalizationService.IncreAddLocalizationConfig(table); + } + } + + private async UniTaskVoid LoadStartupTablesAsync() + { + if (!AppServices.TryGet(out var resourceService)) + { + return; + } + + List loadedTables = new(_startupTableLocations.Count); + for (int i = 0; i < _startupTableLocations.Count; i++) + { + string location = _startupTableLocations[i]; + if (string.IsNullOrEmpty(location)) + { + continue; + } + + GameLocaizationTable table = await resourceService.LoadAssetAsync(location, default, _resourcePackageName); + if (table != null) + { + loadedTables.Add(table); + } + } + + if (loadedTables.Count == 0) + { + return; + } + + if (_startupTables == null) + { + _startupTables = new List(loadedTables.Count); + } + + for (int i = 0; i < loadedTables.Count; i++) + { + GameLocaizationTable table = loadedTables[i]; + if (_startupTables.Contains(table)) + { + continue; + } + + _startupTables.Add(table); + _mLocalizationService.IncreAddLocalizationConfig(table); + } } } } diff --git a/Runtime/Localization/ScriptableObject/GameLocaizationTable.cs b/Runtime/Localization/ScriptableObject/GameLocaizationTable.cs index fd8e0a2..5c86c2c 100644 --- a/Runtime/Localization/ScriptableObject/GameLocaizationTable.cs +++ b/Runtime/Localization/ScriptableObject/GameLocaizationTable.cs @@ -1,8 +1,6 @@ using System; -using System.Linq; using System.Collections.Generic; using UnityEngine; -using UnityEngine.Serialization; namespace AlicizaX.Localization { @@ -45,9 +43,53 @@ namespace AlicizaX.Localization #endif public List Languages = new(); + [NonSerialized] private Dictionary _languageMap; + [NonSerialized] private int _languageMapVersion = -1; + internal LocalizationLanguage GetLanguage(string languageCode) { - return Languages.Find(t => t.LanguageName == languageCode); + if (string.IsNullOrEmpty(languageCode) || Languages == null || Languages.Count == 0) + { + return null; + } + + EnsureLanguageMap(); + _languageMap.TryGetValue(languageCode, out LocalizationLanguage language); + return language; + } + + public void InvalidateLanguageLookup() + { + _languageMapVersion = -1; + } + + private void EnsureLanguageMap() + { + int count = Languages?.Count ?? 0; + if (_languageMap != null && _languageMapVersion == count) + { + return; + } + + _languageMap ??= new Dictionary(count, StringComparer.Ordinal); + _languageMap.Clear(); + + if (Languages != null) + { + for (int i = 0; i < Languages.Count; i++) + { + LocalizationLanguage language = Languages[i]; + if (language == null || string.IsNullOrEmpty(language.LanguageName)) + { + continue; + } + + _languageMap[language.LanguageName] = language; + } + } + + _languageMapVersion = count; } } } + diff --git a/Runtime/Localization/Service/ILocalizationService.cs b/Runtime/Localization/Service/ILocalizationService.cs index 489ba61..7662b60 100644 --- a/Runtime/Localization/Service/ILocalizationService.cs +++ b/Runtime/Localization/Service/ILocalizationService.cs @@ -1,437 +1,447 @@ -using System.Collections.Generic; +using System.Threading; using AlicizaX; +using Cysharp.Threading.Tasks; namespace AlicizaX.Localization.Runtime { /// - /// 本地化管理器接口。 + /// ػӿڡ /// public interface ILocalizationService : IService { public string Language { get; } void Initialize(string language); + bool TryGetRawString(string key, out string value); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典主键。 - /// 要获取的字典内容字符串。 + /// ֵ + /// Ҫȡֵַ string GetString(string key); void ChangedLanguage(string language); + UniTask SwitchLanguageAsync(string language, CancellationToken cancellationToken = default); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数的类型。 - /// 字典主键。 - /// 字典参数列表。 - /// 要获取的字典内容字符串。 + /// ֵ͡ + /// ֵ + /// ֵб + /// Ҫȡֵַ string GetString(string key, params object[] args); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数的类型。 - /// 字典主键。 - /// 字典参数。 - /// 要获取的字典内容字符串。 + /// ֵ͡ + /// ֵ + /// ֵ + /// Ҫȡֵַ string GetString(string key, T arg); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典参数 13 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 字典参数 13。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ 13 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// ֵ 13 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典参数 13 的类型。 - /// 字典参数 14 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 字典参数 13。 - /// 字典参数 14。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ 13 ͡ + /// ֵ 14 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// ֵ 13 + /// ֵ 14 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典参数 13 的类型。 - /// 字典参数 14 的类型。 - /// 字典参数 15 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 字典参数 13。 - /// 字典参数 14。 - /// 字典参数 15。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ 13 ͡ + /// ֵ 14 ͡ + /// ֵ 15 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// ֵ 13 + /// ֵ 14 + /// ֵ 15 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15); /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典参数 13 的类型。 - /// 字典参数 14 的类型。 - /// 字典参数 15 的类型。 - /// 字典参数 16 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 字典参数 13。 - /// 字典参数 14。 - /// 字典参数 15。 - /// 字典参数 16。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ 13 ͡ + /// ֵ 14 ͡ + /// ֵ 15 ͡ + /// ֵ 16 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// ֵ 13 + /// ֵ 14 + /// ֵ 15 + /// ֵ 16 + /// Ҫȡֵַ string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16); /// - /// 根据字典主键获取字典值。 + /// ֵȡֵֵ /// - /// 字典主键。 - /// 字典值。 + /// ֵ + /// ֵֵ string GetRawString(string key); /// - /// 增量增加多语言配置 + /// Ӷ /// /// void IncreAddLocalizationConfig(GameLocaizationTable table); /// - /// 覆盖增加多语言配置 + /// Ӷ /// /// void CoverAddLocalizationConfig(GameLocaizationTable table); + + /// + /// ¼ָԱǰԵݡ + /// + /// ĿԱ + void ReloadLocalizationConfig(GameLocaizationTable table); } } + diff --git a/Runtime/Localization/Service/LocalizationService.cs b/Runtime/Localization/Service/LocalizationService.cs index 328e5d4..44432b0 100644 --- a/Runtime/Localization/Service/LocalizationService.cs +++ b/Runtime/Localization/Service/LocalizationService.cs @@ -1,16 +1,20 @@ -using System; +using System; using System.Collections.Generic; +using System.Threading; using AlicizaX; +using Cysharp.Threading.Tasks; namespace AlicizaX.Localization.Runtime { /// - /// 本地化管理器。 + /// ػ /// [UnityEngine.Scripting.Preserve] internal sealed partial class LocalizationService : ServiceBase, ILocalizationService { private readonly Dictionary Dic = new(); + private readonly List _trackedTables = new(); + private readonly Dictionary> _trackedTableKeys = new(); private string _language; public string Language @@ -20,26 +24,44 @@ namespace AlicizaX.Localization.Runtime public void ChangedLanguage(string language) { - if (_language == language) return; + SwitchLanguageAsync(language).Forget(); + } + + public UniTask SwitchLanguageAsync(string language, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + if (string.IsNullOrEmpty(language) || _language == language) + { + return UniTask.CompletedTask; + } + _language = language; + RebuildTrackedTables(); + LocalizationComponent.SaveLanguagePreference(language); LocalizationChangeEvent.Publisher(_language); + return UniTask.CompletedTask; } public void Initialize(string language) { _language = language; + LocalizationComponent.SaveLanguagePreference(language); Log.Info($"Initializing LocalizationModule :{language}"); } + public bool TryGetRawString(string key, out string value) + { + return Dic.TryGetValue(key, out value); + } + /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典主键。 - /// 要获取的字典内容字符串。 + /// ֵ + /// Ҫȡֵַ public string GetString(string key) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -48,15 +70,14 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典主键。 - /// 参数列表. - /// 要获取的字典内容字符串。 + /// ֵ + /// б. + /// Ҫȡֵַ public string GetString(string key, params object[] args) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -65,16 +86,15 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数的类型。 - /// 字典主键。 - /// 字典参数。 - /// 要获取的字典内容字符串。 + /// ֵ͡ + /// ֵ + /// ֵ + /// Ҫȡֵַ public string GetString(string key, T arg) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -90,18 +110,17 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -117,20 +136,19 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -146,22 +164,21 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -177,24 +194,23 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -210,26 +226,25 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -245,28 +260,27 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -282,30 +296,29 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -321,32 +334,31 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -362,34 +374,33 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -405,36 +416,35 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -450,38 +460,37 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -497,40 +506,39 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典参数 13 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 字典参数 13。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ 13 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// ֵ 13 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -546,42 +554,41 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典参数 13 的类型。 - /// 字典参数 14 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 字典参数 13。 - /// 字典参数 14。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ 13 ͡ + /// ֵ 14 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// ֵ 13 + /// ֵ 14 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -598,44 +605,43 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典参数 13 的类型。 - /// 字典参数 14 的类型。 - /// 字典参数 15 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 字典参数 13。 - /// 字典参数 14。 - /// 字典参数 15。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ 13 ͡ + /// ֵ 14 ͡ + /// ֵ 15 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// ֵ 13 + /// ֵ 14 + /// ֵ 15 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -652,47 +658,46 @@ namespace AlicizaX.Localization.Runtime } /// - /// 根据字典主键获取字典内容字符串。 + /// ֵȡֵַ /// - /// 字典参数 1 的类型。 - /// 字典参数 2 的类型。 - /// 字典参数 3 的类型。 - /// 字典参数 4 的类型。 - /// 字典参数 5 的类型。 - /// 字典参数 6 的类型。 - /// 字典参数 7 的类型。 - /// 字典参数 8 的类型。 - /// 字典参数 9 的类型。 - /// 字典参数 10 的类型。 - /// 字典参数 11 的类型。 - /// 字典参数 12 的类型。 - /// 字典参数 13 的类型。 - /// 字典参数 14 的类型。 - /// 字典参数 15 的类型。 - /// 字典参数 16 的类型。 - /// 字典主键。 - /// 字典参数 1。 - /// 字典参数 2。 - /// 字典参数 3。 - /// 字典参数 4。 - /// 字典参数 5。 - /// 字典参数 6。 - /// 字典参数 7。 - /// 字典参数 8。 - /// 字典参数 9。 - /// 字典参数 10。 - /// 字典参数 11。 - /// 字典参数 12。 - /// 字典参数 13。 - /// 字典参数 14。 - /// 字典参数 15。 - /// 字典参数 16。 - /// 要获取的字典内容字符串。 + /// ֵ 1 ͡ + /// ֵ 2 ͡ + /// ֵ 3 ͡ + /// ֵ 4 ͡ + /// ֵ 5 ͡ + /// ֵ 6 ͡ + /// ֵ 7 ͡ + /// ֵ 8 ͡ + /// ֵ 9 ͡ + /// ֵ 10 ͡ + /// ֵ 11 ͡ + /// ֵ 12 ͡ + /// ֵ 13 ͡ + /// ֵ 14 ͡ + /// ֵ 15 ͡ + /// ֵ 16 ͡ + /// ֵ + /// ֵ 1 + /// ֵ 2 + /// ֵ 3 + /// ֵ 4 + /// ֵ 5 + /// ֵ 6 + /// ֵ 7 + /// ֵ 8 + /// ֵ 9 + /// ֵ 10 + /// ֵ 11 + /// ֵ 12 + /// ֵ 13 + /// ֵ 14 + /// ֵ 15 + /// ֵ 16 + /// Ҫȡֵַ public string GetString(string key, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) { - var value = GetRawString(key); - if (value == null) + if (!Dic.TryGetValue(key, out string value)) { return Utility.Text.Format("{0}", key); } @@ -710,43 +715,28 @@ namespace AlicizaX.Localization.Runtime public string GetRawString(string key) { - if (Dic.TryGetValue(key, out string value)) - { - return value; - } - - return key; + return TryGetRawString(key, out string value) ? value : null; } public void IncreAddLocalizationConfig(GameLocaizationTable table) { - LocalizationLanguage localizationLanguage = table.GetLanguage(_language); - if (localizationLanguage == null) - { - Log.Warning($"Can not Find {_language} Strins "); - return; - } - - foreach (var item in localizationLanguage.Strings) - { - Dic.Add(item.Key, item.Value); - } + TrackTable(table); + ReapplyTrackedTable(table); } public void CoverAddLocalizationConfig(GameLocaizationTable table) { Dic.Clear(); - LocalizationLanguage localizationLanguage = table.GetLanguage(_language); - if (localizationLanguage == null) - { - Log.Warning($"Can not Find {_language} Strins "); - return; - } + _trackedTables.Clear(); + _trackedTableKeys.Clear(); + TrackTable(table); + ReapplyTrackedTable(table); + } - foreach (var item in localizationLanguage.Strings) - { - Dic.Add(item.Key, item.Value); - } + public void ReloadLocalizationConfig(GameLocaizationTable table) + { + TrackTable(table); + ReapplyTrackedTable(table); } protected override void OnInitialize() { } @@ -754,6 +744,76 @@ namespace AlicizaX.Localization.Runtime protected override void OnDestroyService() { Dic.Clear(); + _trackedTables.Clear(); + _trackedTableKeys.Clear(); + } + + private void TrackTable(GameLocaizationTable table) + { + if (table == null) + { + return; + } + + if (!_trackedTables.Contains(table)) + { + _trackedTables.Add(table); + } + } + + private void RebuildTrackedTables() + { + Dic.Clear(); + _trackedTableKeys.Clear(); + + for (int i = 0; i < _trackedTables.Count; i++) + { + ReapplyTrackedTable(_trackedTables[i]); + } + } + + private void ReapplyTrackedTable(GameLocaizationTable table) + { + if (table == null) + { + return; + } + + RemoveTrackedTableEntries(table); + + LocalizationLanguage localizationLanguage = table.GetLanguage(_language); + if (localizationLanguage == null) + { + Log.Warning($"Can not Find {_language} Strins "); + _trackedTableKeys[table] = new List(0); + return; + } + + Dic.EnsureCapacity(Dic.Count + localizationLanguage.Strings.Count); + List keys = new(localizationLanguage.Strings.Count); + foreach (var item in localizationLanguage.Strings) + { + Dic[item.Key] = item.Value; + keys.Add(item.Key); + } + + _trackedTableKeys[table] = keys; + } + + private void RemoveTrackedTableEntries(GameLocaizationTable table) + { + if (!_trackedTableKeys.TryGetValue(table, out List keys)) + { + return; + } + + for (int i = 0; i < keys.Count; i++) + { + Dic.Remove(keys[i]); + } } } } + + + diff --git a/Runtime/Procedure/IProcedureService.cs b/Runtime/Procedure/IProcedureService.cs index 41bce6f..d46f68b 100644 --- a/Runtime/Procedure/IProcedureService.cs +++ b/Runtime/Procedure/IProcedureService.cs @@ -3,11 +3,25 @@ using System.Collections.Generic; namespace AlicizaX { - public interface IProcedureService:IService, IServiceTickable + public interface IProcedureService : IService, IServiceTickable { - void InitializeProcedure(List availableProcedures, Type defaultProcedureType); + Type CurrentProcedureType { get; } + void InitializeProcedure(IEnumerable availableProcedures, Type defaultProcedureType); void ClearAllProcedures(); - bool SwitchProcedure() where T : IProcedure; - bool SwitchProcedure(Type procedureType); + bool ContainsProcedure(Type procedureType); + bool TrySwitchProcedure(Type procedureType); + } + + public static class ProcedureServiceExtensions + { + public static bool SwitchProcedure(this IProcedureService procedureService) where T : IProcedure + { + return procedureService.TrySwitchProcedure(typeof(T)); + } + + public static bool SwitchProcedure(this IProcedureService procedureService, Type procedureType) + { + return procedureService.TrySwitchProcedure(procedureType); + } } } diff --git a/Runtime/Procedure/ProcedureComponent.cs b/Runtime/Procedure/ProcedureComponent.cs index a29d8f5..17382c5 100644 --- a/Runtime/Procedure/ProcedureComponent.cs +++ b/Runtime/Procedure/ProcedureComponent.cs @@ -8,7 +8,8 @@ namespace AlicizaX { private void Awake() { - AppServices.GetOrCreateScope().Register(new ProcedureService()); + AppServices.App.Register(new ProcedureService()); } } } + diff --git a/Runtime/Procedure/ProcedureService.cs b/Runtime/Procedure/ProcedureService.cs index ac54342..109decd 100644 --- a/Runtime/Procedure/ProcedureService.cs +++ b/Runtime/Procedure/ProcedureService.cs @@ -10,113 +10,83 @@ namespace AlicizaX private IProcedure _currentProcedure; private IProcedure _defaultProcedure; + public Type CurrentProcedureType => _currentProcedure?.GetType(); - public void InitializeProcedure(List availableProcedures, Type defaultProcedureType) + public void InitializeProcedure(IEnumerable availableProcedures, Type defaultProcedureType) { _procedures.Clear(); foreach (var procedure in availableProcedures) { + if (procedure == null) + { + continue; + } + var type = procedure.GetType(); _procedures[type] = procedure; procedure.ProcedureService = this; procedure.Init(); } - if (_procedures.ContainsKey(defaultProcedureType)) + if (_procedures.TryGetValue(defaultProcedureType, out var defaultProcedure)) { - _defaultProcedure = _procedures[defaultProcedureType]; - SwitchProcedure(defaultProcedureType); + _defaultProcedure = defaultProcedure; + TrySwitchProcedure(defaultProcedureType); } else { - Log.Info($"默认流程 {defaultProcedureType.Name} 未注册!"); + Log.Info($"榛樿娴佺▼ {defaultProcedureType.Name} 鏈敞鍐?"); } } - - /// - /// - /// 清除所有流程 - /// public void ClearAllProcedures() { - if (_procedures != null) + foreach (var procedure in _procedures.Values) { - foreach (var procedure in _procedures.Values) - { - procedure.Destroy(); - } - - _procedures.Clear(); + procedure.Destroy(); } + _procedures.Clear(); _currentProcedure = null; _defaultProcedure = null; } - public bool SwitchProcedure() where T : IProcedure + public bool ContainsProcedure(Type procedureType) { - if (HasProcedure()) - { - return SwitchProcedure(typeof(T)); - } - - if (_defaultProcedure != null) - { - Debug.LogWarning($"流程 {typeof(T).Name} 不存在,切换到默认流程"); - return SwitchToDefault(); - } - - return false; + return procedureType != null && _procedures.ContainsKey(procedureType); } - - public bool SwitchProcedure(Type procedureType) + public bool TrySwitchProcedure(Type procedureType) { - var nextProcedure = _procedures[procedureType]; - var lastProcedure = _currentProcedure; - - if (lastProcedure == nextProcedure) - return true; - - if (lastProcedure != null) + if (procedureType == null) { - lastProcedure.Leave(); + return false; } + if (!_procedures.TryGetValue(procedureType, out var nextProcedure)) + { + if (_defaultProcedure == null) + { + return false; + } + + Debug.LogWarning($"娴佺▼ {procedureType.Name} 涓嶅瓨鍦紝鍒囨崲鍒伴粯璁ゆ祦绋?"); + nextProcedure = _defaultProcedure; + } + + if (ReferenceEquals(_currentProcedure, nextProcedure)) + { + return true; + } + + _currentProcedure?.Leave(); nextProcedure.Enter(); _currentProcedure = nextProcedure; - return true; } - - /// - /// 切换到默认流程 - /// - private bool SwitchToDefault() - { - if (_defaultProcedure != null) - { - return SwitchProcedure(_defaultProcedure.GetType()); - } - - return false; - } - - - /// - /// 检查是否存在指定类型的流程 - /// - private bool HasProcedure() where T : IProcedure - { - return _procedures.ContainsKey(typeof(T)); - } - - protected override void OnInitialize() { - } protected override void OnDestroyService() @@ -126,10 +96,7 @@ namespace AlicizaX void IServiceTickable.Tick(float deltaTime) { - if (_currentProcedure != null) - { - _currentProcedure.Update(); - } + _currentProcedure?.Update(); } } } diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs index fcac2ea..89150a3 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.Resource.cs @@ -66,7 +66,7 @@ namespace AlicizaX.Resource.Runtime } } - public async UniTaskVoid SetAssetByResources(ISetAssetObject setAssetObject, CancellationToken cancellationToken) where T : UnityEngine.Object + public async UniTask SetAssetByResources(ISetAssetObject setAssetObject, CancellationToken cancellationToken) where T : UnityEngine.Object { var target = setAssetObject.TargetObject; var location = setAssetObject.Location; @@ -176,6 +176,7 @@ namespace AlicizaX.Resource.Runtime private void OnDestroy() { + UnityEngine.Application.lowMemory -= OnLowMemory; ReleaseTrackedAssets(); foreach (var state in _loadingStates.Values) @@ -186,4 +187,4 @@ namespace AlicizaX.Resource.Runtime _loadingStates.Clear(); } } -} \ No newline at end of file +} diff --git a/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs b/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs index 9598ba4..45becb8 100644 --- a/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs +++ b/Runtime/Resource/Resource/Extension/ResourceExtComponent.cs @@ -23,8 +23,6 @@ namespace AlicizaX.Resource.Runtime [SerializeField] private float checkCanReleaseInterval = 30f; - private float _checkCanReleaseTime = 0.0f; - /// /// 对象池自动释放时间间隔。 /// @@ -37,6 +35,9 @@ namespace AlicizaX.Resource.Runtime [SerializeField] private int maxProcessPerFrame = 50; + [SerializeField] + private int releaseCheckThreshold = 16; + /// /// 当前正在处理的节点,用于分帧处理。 /// @@ -48,6 +49,8 @@ namespace AlicizaX.Resource.Runtime private LinkedList _loadAssetObjectsLinkedList; private Dictionary> _trackedAssetNodes; + private bool _releaseRequested; + private float _nextReleaseCheckTime = float.MaxValue; /// /// 散图集合对象池。 @@ -65,11 +68,18 @@ namespace AlicizaX.Resource.Runtime private IEnumerator Start() { Instance = this; + enabled = false; + Application.lowMemory += OnLowMemory; yield return new WaitForEndOfFrame(); IObjectPoolService objectPoolComponent = AppServices.Require(); - _assetItemPool = objectPoolComponent.CreateMultiSpawnObjectPool( - "SetAssetPool", - autoReleaseInterval, 16, 60, 0); + _assetItemPool = objectPoolComponent.CreatePool( + new ObjectPoolCreateOptions( + name: "SetAssetPool", + allowMultiSpawn: true, + autoReleaseInterval: autoReleaseInterval, + capacity: 16, + expireTime: 60, + priority: 0)); _loadAssetObjectsLinkedList = new LinkedList(); _trackedAssetNodes = new Dictionary>(16); @@ -78,8 +88,13 @@ namespace AlicizaX.Resource.Runtime private void Update() { - _checkCanReleaseTime += Time.unscaledDeltaTime; - if (_checkCanReleaseTime < (double)checkCanReleaseInterval) + if (!_releaseRequested) + { + enabled = false; + return; + } + + if (Time.unscaledTime < _nextReleaseCheckTime) { return; } @@ -96,7 +111,9 @@ namespace AlicizaX.Resource.Runtime if (_loadAssetObjectsLinkedList == null || _loadAssetObjectsLinkedList.Count == 0) { _currentProcessNode = null; - _checkCanReleaseTime = 0f; + _releaseRequested = false; + _nextReleaseCheckTime = float.MaxValue; + enabled = false; return; } @@ -129,7 +146,13 @@ namespace AlicizaX.Resource.Runtime // 如果已经处理完所有节点,重置状态 if (_currentProcessNode == null) { - _checkCanReleaseTime = 0f; + _releaseRequested = false; + _nextReleaseCheckTime = float.MaxValue; + enabled = false; + } + else + { + ScheduleReleaseSweep(checkCanReleaseInterval); } } @@ -144,6 +167,10 @@ namespace AlicizaX.Resource.Runtime } setAssetObject.SetAsset(assetObject); + if (_loadAssetObjectsLinkedList.Count >= releaseCheckThreshold) + { + ScheduleReleaseSweep(); + } } private void ReplaceTrackedAsset(Object target) @@ -188,6 +215,10 @@ namespace AlicizaX.Resource.Runtime } _loadAssetObjectsLinkedList?.Remove(node); + if (_loadAssetObjectsLinkedList != null && _loadAssetObjectsLinkedList.Count > 0) + { + ScheduleReleaseSweep(checkCanReleaseInterval); + } } private void ReleaseTrackedAssets() @@ -207,6 +238,22 @@ namespace AlicizaX.Resource.Runtime _currentProcessNode = null; _trackedAssetNodes?.Clear(); + _releaseRequested = false; + _nextReleaseCheckTime = float.MaxValue; + enabled = false; + } + + private void ScheduleReleaseSweep(float delay = 0f) + { + _releaseRequested = true; + float dueTime = Time.unscaledTime + Math.Max(0f, delay); + _nextReleaseCheckTime = Math.Min(_nextReleaseCheckTime, dueTime); + enabled = true; + } + + private void OnLowMemory() + { + ScheduleReleaseSweep(); } } } diff --git a/Runtime/Resource/Resource/IResourceService.cs b/Runtime/Resource/Resource/IResourceService.cs index 573404e..fdaa952 100644 --- a/Runtime/Resource/Resource/IResourceService.cs +++ b/Runtime/Resource/Resource/IResourceService.cs @@ -151,7 +151,7 @@ namespace AlicizaX.Resource.Runtime /// 加载资源回调函数集。 /// 用户自定义数据。 /// 指定资源包的名称。不传使用默认资源包。 - void LoadAssetAsync(string location, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = ""); + UniTask LoadAssetAsync(string location, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = ""); /// /// 异步加载资源。 @@ -162,7 +162,7 @@ namespace AlicizaX.Resource.Runtime /// 加载资源回调函数集。 /// 用户自定义数据。 /// 指定资源包的名称。不传使用默认资源包。 - void LoadAssetAsync(string location, Type assetType, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = ""); + UniTask LoadAssetAsync(string location, Type assetType, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = ""); /// /// 同步加载资源。 @@ -190,7 +190,7 @@ namespace AlicizaX.Resource.Runtime /// 回调函数。 /// 指定资源包的名称。不传使用默认资源包 /// 要加载资源的类型。 - UniTaskVoid LoadAsset(string location, Action callback, string packageName = "") where T : UnityEngine.Object; + UniTask LoadAsset(string location, Action callback, string packageName = "") where T : UnityEngine.Object; /// /// 异步加载资源。 diff --git a/Runtime/Resource/Resource/ResourceComponent.cs b/Runtime/Resource/Resource/ResourceComponent.cs index b60fad2..50a0340 100644 --- a/Runtime/Resource/Resource/ResourceComponent.cs +++ b/Runtime/Resource/Resource/ResourceComponent.cs @@ -7,7 +7,7 @@ using YooAsset; namespace AlicizaX.Resource.Runtime { /// - /// 资源组件。 + /// 资源组件�? /// [DisallowMultipleComponent] public class ResourceComponent : MonoBehaviour @@ -41,24 +41,24 @@ namespace AlicizaX.Resource.Runtime [SerializeField] private string decryptionServices = ""; /// - /// 自动释放资源引用计数为0的资源包 + /// 自动释放资源引用计数�?的资源包 /// [SerializeField] public bool autoUnloadBundleWhenUnused = false; [SerializeField] private EPlayMode _playMode = EPlayMode.EditorSimulateMode; /// - /// 当前最新的包裹版本。 + /// 当前最新的包裹版本�? /// public string PackageVersion { set; get; } /// - /// 资源包名称。 + /// 资源包名称�? /// [SerializeField] private string packageName = "DefaultPackage"; /// - /// 资源包名称。 + /// 资源包名称�? /// public string PackageName { @@ -75,7 +75,7 @@ namespace AlicizaX.Resource.Runtime public int downloadingMaxNum = 10; /// - /// 获取或设置同时最大下载数目。 + /// 获取或设置同时最大下载数目�? /// public int DownloadingMaxNum { @@ -92,17 +92,17 @@ namespace AlicizaX.Resource.Runtime } /// - /// 获取当前资源适用的游戏版本号。 + /// 获取当前资源适用的游戏版本号�? /// public string ApplicableGameVersion => _resourceService.ApplicableGameVersion; /// - /// 获取当前内部资源版本号。 + /// 获取当前内部资源版本号�? /// public int InternalResourceVersion => _resourceService.InternalResourceVersion; /// - /// 获取或设置无用资源释放的最小间隔时间,以秒为单位。 + /// 获取或设置无用资源释放的最小间隔时间,以秒为单位�? /// public float MinUnloadUnusedAssetsInterval { @@ -111,7 +111,7 @@ namespace AlicizaX.Resource.Runtime } /// - /// 获取或设置无用资源释放的最大间隔时间,以秒为单位。 + /// 获取或设置无用资源释放的最大间隔时间,以秒为单位�? /// public float MaxUnloadUnusedAssetsInterval { @@ -120,7 +120,7 @@ namespace AlicizaX.Resource.Runtime } /// - /// 使用系统释放无用资源策略。 + /// 使用系统释放无用资源策略�? /// public bool UseSystemUnloadUnusedAssets { @@ -129,7 +129,7 @@ namespace AlicizaX.Resource.Runtime } /// - /// 获取无用资源释放的等待时长,以秒为单位。 + /// 获取无用资源释放的等待时长,以秒为单位�? /// public float LastUnloadUnusedAssetsOperationElapseSeconds => _lastUnloadUnusedAssetsOperationElapseSeconds; @@ -142,7 +142,7 @@ namespace AlicizaX.Resource.Runtime [SerializeField] private int assetPriority = 0; /// - /// 获取或设置资源对象池自动释放可释放对象的间隔秒数。 + /// 获取或设置资源对象池自动释放可释放对象的间隔秒数�? /// public float AssetAutoReleaseInterval { @@ -151,7 +151,7 @@ namespace AlicizaX.Resource.Runtime } /// - /// 获取或设置资源对象池的容量。 + /// 获取或设置资源对象池的容量�? /// public int AssetCapacity { @@ -160,7 +160,7 @@ namespace AlicizaX.Resource.Runtime } /// - /// 获取或设置资源对象池对象过期秒数。 + /// 获取或设置资源对象池对象过期秒数�? /// public float AssetExpireTime { @@ -169,7 +169,7 @@ namespace AlicizaX.Resource.Runtime } /// - /// 获取或设置资源对象池的优先级。 + /// 获取或设置资源对象池的优先级�? /// public int AssetPriority { @@ -191,7 +191,7 @@ namespace AlicizaX.Resource.Runtime private void Awake() { - _resourceService = AppServices.GetOrCreateScope().Register(new ResourceService()); + _resourceService = AppServices.App.Register(new ResourceService()); Application.lowMemory += OnLowMemory; } @@ -227,9 +227,9 @@ namespace AlicizaX.Resource.Runtime #region 释放资源 /// - /// 强制执行释放未被使用的资源。 + /// 强制执行释放未被使用的资源�? /// - /// 是否使用垃圾回收。 + /// 是否使用垃圾回收�?/param> public void ForceUnloadUnusedAssets(bool performGCCollect) { _forceUnloadUnusedAssets = true; diff --git a/Runtime/Resource/Resource/ResourceService.Pool.cs b/Runtime/Resource/Resource/ResourceService.Pool.cs index 7ef6296..9073fa8 100644 --- a/Runtime/Resource/Resource/ResourceService.Pool.cs +++ b/Runtime/Resource/Resource/ResourceService.Pool.cs @@ -61,7 +61,8 @@ namespace AlicizaX.Resource.Runtime /// 对象池管理器。 public void CreateAssetPool( ) { - _assetPool = Context.Require().CreateMultiSpawnObjectPool("Asset Pool"); + _assetPool = Context.Require().CreatePool( + ObjectPoolCreateOptions.Multi("Asset Pool")); } } } diff --git a/Runtime/Resource/Resource/ResourceService.cs b/Runtime/Resource/Resource/ResourceService.cs index 1f84ba6..6719a2d 100644 --- a/Runtime/Resource/Resource/ResourceService.cs +++ b/Runtime/Resource/Resource/ResourceService.cs @@ -652,7 +652,7 @@ namespace AlicizaX.Resource.Runtime /// 回调函数。 /// 指定资源包的名称。不传使用默认资源包 /// 要加载资源的类型。 - public async UniTaskVoid LoadAsset(string location, Action callback, string packageName = "") where T : UnityEngine.Object + public async UniTask LoadAsset(string location, Action callback, string packageName = "") where T : UnityEngine.Object { if (string.IsNullOrEmpty(location)) { @@ -730,7 +730,7 @@ namespace AlicizaX.Resource.Runtime /// 加载资源回调函数集。 /// 用户自定义数据。 /// 指定资源包的名称。不传使用默认资源包。 - public async void LoadAssetAsync(string location, Type assetType, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = "") + public async UniTask LoadAssetAsync(string location, Type assetType, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = "") { if (string.IsNullOrEmpty(location)) { @@ -800,7 +800,7 @@ namespace AlicizaX.Resource.Runtime /// 加载资源回调函数集。 /// 用户自定义数据。 /// 指定资源包的名称。不传使用默认资源包。 - public async void LoadAssetAsync(string location, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = "") + public async UniTask LoadAssetAsync(string location, int priority, LoadAssetCallbacks loadAssetCallbacks, object userData, string packageName = "") { if (string.IsNullOrEmpty(location)) { diff --git a/Runtime/Scene/ISceneService.cs b/Runtime/Scene/ISceneService.cs index a13d4ac..ec0cdf0 100644 --- a/Runtime/Scene/ISceneService.cs +++ b/Runtime/Scene/ISceneService.cs @@ -1,41 +1,17 @@ using System; -using AlicizaX.Resource.Runtime; using AlicizaX; using Cysharp.Threading.Tasks; using UnityEngine.SceneManagement; -using YooAsset; namespace AlicizaX.Scene.Runtime { public interface ISceneService : IService { - /// - /// 当前主场景名称。 - /// public string CurrentMainSceneName { get; } - /// - /// 加载场景。 - /// - /// 场景的定位地址 - /// 场景加载模式 - /// 加载完毕时是否主动挂起 - /// 优先级 - /// 加载主场景是否回收垃圾。 - /// 加载进度回调。 public UniTask LoadSceneAsync(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool suspendLoad = false, uint priority = 100, bool gcCollect = true, Action progressCallBack = null); - /// - /// 加载场景。 - /// - /// 场景的定位地址 - /// 场景加载模式 - /// 加载完毕时是否主动挂起 - /// 优先级 - /// 加载回调。 - /// 加载主场景是否回收垃圾。 - /// 加载进度回调。 public void LoadScene(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool suspendLoad = false, @@ -44,47 +20,25 @@ namespace AlicizaX.Scene.Runtime bool gcCollect = true, Action progressCallBack = null); - /// - /// 激活场景(当同时存在多个场景时用于切换激活场景)。 - /// - /// 场景资源定位地址。 - /// 是否操作成功。 public bool ActivateScene(string location); - /// - /// 解除场景加载挂起操作。 - /// - /// 场景资源定位地址。 - /// 是否操作成功。 public bool UnSuspend(string location); - /// - /// 是否为主场景。 - /// - /// 场景资源定位地址。 - /// 是否主场景。 public bool IsMainScene(string location); - /// - /// 异步卸载子场景。 - /// - /// 场景资源定位地址。 - /// 进度回调。 public UniTask UnloadAsync(string location, Action progressCallBack = null); - /// - /// 异步卸载子场景。 - /// - /// 场景资源定位地址。 - /// 卸载完成回调。 - /// 进度回调。 public void Unload(string location, Action callBack = null, Action progressCallBack = null); - /// - /// 是否包含场景。 - /// - /// 场景资源定位地址。 - /// 是否包含场景。 public bool IsContainScene(string location); } + + public interface ISceneStateService : IService + { + public string CurrentMainSceneName { get; } + + public bool IsContainScene(string location); + + public bool IsMainScene(string location); + } } diff --git a/Runtime/Scene/SceneComponent.cs b/Runtime/Scene/SceneComponent.cs index c728ec2..5b69994 100644 --- a/Runtime/Scene/SceneComponent.cs +++ b/Runtime/Scene/SceneComponent.cs @@ -1,4 +1,3 @@ -using System; using AlicizaX; using UnityEngine; @@ -10,7 +9,12 @@ namespace AlicizaX.Scene.Runtime { private void Awake() { - AppServices.GetOrCreateScope().Register(new SceneService()); + if (!AppServices.App.TryGet(out _)) + { + AppServices.App.Register(new SceneService()); + } + + AppServices.EnsureScene(); } } } diff --git a/Runtime/Scene/SceneService.cs b/Runtime/Scene/SceneService.cs index d7741df..647b1ea 100644 --- a/Runtime/Scene/SceneService.cs +++ b/Runtime/Scene/SceneService.cs @@ -9,56 +9,22 @@ namespace AlicizaX.Scene.Runtime { internal class SceneService : ServiceBase, ISceneService { - private string _currentMainSceneName = string.Empty; - - private SceneHandle _currentMainScene; - - private readonly Dictionary _subScenes = new Dictionary(); - - private readonly HashSet _handlingScene = new HashSet(); - - /// - /// 当前主场景名称。 - /// - public string CurrentMainSceneName => _currentMainSceneName; + public string CurrentMainSceneName => EnsureSceneState().CurrentMainSceneName; protected override void OnInitialize() { - _currentMainScene = null; - _currentMainSceneName = SceneManager.GetSceneByBuildIndex(0).name; + var activeScene = SceneManager.GetActiveScene(); + EnsureSceneState().SetBootScene(activeScene.name); } protected override void OnDestroyService() { - var iter = _subScenes.Values.GetEnumerator(); - while (iter.MoveNext()) - { - SceneHandle subScene = iter.Current; - if (subScene != null) - { - subScene.UnloadAsync(); - } - } - - iter.Dispose(); - _subScenes.Clear(); - _handlingScene.Clear(); - _currentMainSceneName = string.Empty; } - - /// - /// 加载场景。 - /// - /// 场景的定位地址 - /// 场景加载模式 - /// 加载完毕时是否主动挂起 - /// 优先级 - /// 加载主场景是否回收垃圾。 - /// 加载进度回调。 public async UniTask LoadSceneAsync(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool suspendLoad = false, uint priority = 100, bool gcCollect = true, Action progressCallBack = null) { - if (!_handlingScene.Add(location)) + var sceneState = EnsureSceneState(); + if (!sceneState.TryBeginHandling(location)) { Log.Error($"Could not load scene while loading. Scene: {location}"); return default; @@ -66,15 +32,13 @@ namespace AlicizaX.Scene.Runtime if (sceneMode == LoadSceneMode.Additive) { - if (_subScenes.TryGetValue(location, out SceneHandle subScene)) + if (sceneState.TryGetSubScene(location, out SceneHandle loadedSubScene)) { throw new Exception($"Could not load subScene while already loaded. Scene: {location}"); } - subScene = YooAssets.LoadSceneAsync(location, sceneMode, LocalPhysicsMode.None, suspendLoad, priority); - - //Fix 这里前置,subScene.IsDone在UnSupendLoad之后才会是true - _subScenes.Add(location, subScene); + var subScene = YooAssets.LoadSceneAsync(location, sceneMode, LocalPhysicsMode.None, suspendLoad, priority); + sceneState.AddSubScene(location, subScene); if (progressCallBack != null) { @@ -89,57 +53,43 @@ namespace AlicizaX.Scene.Runtime await subScene.ToUniTask(); } - _handlingScene.Remove(location); - + sceneState.EndHandling(location); return subScene.SceneObject; } + + if (sceneState.CurrentMainSceneHandle is { IsDone: false }) + { + throw new Exception($"Could not load MainScene while loading. CurrentMainScene: {sceneState.CurrentMainSceneName}."); + } + + sceneState = PrepareSceneStateForMainSceneLoad(location); + var mainSceneHandle = YooAssets.LoadSceneAsync(location, sceneMode, LocalPhysicsMode.None, suspendLoad, priority); + + if (progressCallBack != null) + { + while (!mainSceneHandle.IsDone && mainSceneHandle.IsValid) + { + progressCallBack.Invoke(mainSceneHandle.Progress); + await UniTask.Yield(); + } + } else { - if (_currentMainScene is { IsDone: false }) - { - throw new Exception($"Could not load MainScene while loading. CurrentMainScene: {_currentMainSceneName}."); - } - - _currentMainSceneName = location; - - _currentMainScene = YooAssets.LoadSceneAsync(location, sceneMode, LocalPhysicsMode.None, suspendLoad, priority); - - if (progressCallBack != null) - { - while (!_currentMainScene.IsDone && _currentMainScene.IsValid) - { - progressCallBack.Invoke(_currentMainScene.Progress); - await UniTask.Yield(); - } - } - else - { - await _currentMainScene.ToUniTask(); - } - - Context.Require().ForceUnloadUnusedAssets(gcCollect); - - _handlingScene.Remove(location); - - return _currentMainScene.SceneObject; + await mainSceneHandle.ToUniTask(); } + + sceneState.SetMainScene(location, mainSceneHandle); + Context.Require().ForceUnloadUnusedAssets(gcCollect); + sceneState.EndHandling(location); + return mainSceneHandle.SceneObject; } - /// - /// 加载场景。 - /// - /// 场景的定位地址 - /// 场景加载模式 - /// 加载完毕时是否主动挂起 - /// 优先级 - /// 加载回调。 - /// 加载主场景是否回收垃圾。 - /// 加载进度回调。 public void LoadScene(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool suspendLoad = false, uint priority = 100, Action callBack = null, bool gcCollect = true, Action progressCallBack = null) { - if (!_handlingScene.Add(location)) + var sceneState = EnsureSceneState(); + if (!sceneState.TryBeginHandling(location)) { Log.Error($"Could not load scene while loading. Scene: {location}"); return; @@ -147,88 +97,60 @@ namespace AlicizaX.Scene.Runtime if (sceneMode == LoadSceneMode.Additive) { - if (_subScenes.TryGetValue(location, out SceneHandle subScene)) + if (sceneState.TryGetSubScene(location, out SceneHandle loadedSubScene)) { Log.Warning($"Could not load subScene while already loaded. Scene: {location}"); return; } - subScene = YooAssets.LoadSceneAsync(location, sceneMode, LocalPhysicsMode.None, suspendLoad, priority); - + var subScene = YooAssets.LoadSceneAsync(location, sceneMode, LocalPhysicsMode.None, suspendLoad, priority); + sceneState.AddSubScene(location, subScene); subScene.Completed += handle => { - _handlingScene.Remove(location); + sceneState.EndHandling(location); callBack?.Invoke(handle.SceneObject); }; if (progressCallBack != null) { - InvokeProgress(subScene, progressCallBack).Forget(); + InvokeSceneProgress(subScene, progressCallBack).Forget(); } - _subScenes.Add(location, subScene); - } - else - { - if (_currentMainScene is { IsDone: false }) - { - Log.Warning($"Could not load MainScene while loading. CurrentMainScene: {_currentMainSceneName}."); - return; - } - - _currentMainSceneName = location; - - _currentMainScene = YooAssets.LoadSceneAsync(location, sceneMode, LocalPhysicsMode.None, suspendLoad, priority); - - _currentMainScene.Completed += handle => - { - _handlingScene.Remove(location); - callBack?.Invoke(handle.SceneObject); - }; - - if (progressCallBack != null) - { - InvokeProgress(_currentMainScene, progressCallBack).Forget(); - } - - Context.Require().ForceUnloadUnusedAssets(gcCollect); - } - } - - private async UniTaskVoid InvokeProgress(SceneHandle sceneHandle, Action progress) - { - if (sceneHandle == null) - { return; } - while (!sceneHandle.IsDone && sceneHandle.IsValid) + if (sceneState.CurrentMainSceneHandle is { IsDone: false }) { - await UniTask.Yield(); - - progress?.Invoke(sceneHandle.Progress); + Log.Warning($"Could not load MainScene while loading. CurrentMainScene: {sceneState.CurrentMainSceneName}."); + return; } + + sceneState = PrepareSceneStateForMainSceneLoad(location); + var mainSceneHandle = YooAssets.LoadSceneAsync(location, sceneMode, LocalPhysicsMode.None, suspendLoad, priority); + mainSceneHandle.Completed += handle => + { + sceneState.SetMainScene(location, handle); + sceneState.EndHandling(location); + callBack?.Invoke(handle.SceneObject); + }; + + if (progressCallBack != null) + { + InvokeSceneProgress(mainSceneHandle, progressCallBack).Forget(); + } + + Context.Require().ForceUnloadUnusedAssets(gcCollect); } - /// - /// 激活场景(当同时存在多个场景时用于切换激活场景)。 - /// - /// 场景资源定位地址。 - /// 是否操作成功。 public bool ActivateScene(string location) { - if (_currentMainSceneName.Equals(location)) + var sceneState = EnsureSceneState(); + if (sceneState.CurrentMainSceneName.Equals(location)) { - if (_currentMainScene != null) - { - return _currentMainScene.ActivateScene(); - } - - return false; + return sceneState.CurrentMainSceneHandle != null && sceneState.CurrentMainSceneHandle.ActivateScene(); } - _subScenes.TryGetValue(location, out SceneHandle subScene); - if (subScene != null) + if (sceneState.TryGetSubScene(location, out var subScene) && subScene != null) { return subScene.ActivateScene(); } @@ -237,25 +159,16 @@ namespace AlicizaX.Scene.Runtime return false; } - /// - /// 解除场景加载挂起操作。 - /// - /// 场景资源定位地址。 - /// 是否操作成功。 + public bool UnSuspend(string location) { - if (_currentMainSceneName.Equals(location)) + var sceneState = EnsureSceneState(); + if (sceneState.CurrentMainSceneName.Equals(location)) { - if (_currentMainScene != null) - { - return _currentMainScene.UnSuspend(); - } - - return false; + return sceneState.CurrentMainSceneHandle != null && sceneState.CurrentMainSceneHandle.UnSuspend(); } - _subScenes.TryGetValue(location, out SceneHandle subScene); - if (subScene != null) + if (sceneState.TryGetSubScene(location, out var subScene) && subScene != null) { return subScene.UnSuspend(); } @@ -264,34 +177,27 @@ namespace AlicizaX.Scene.Runtime return false; } - /// - /// 是否为主场景。 - /// - /// 场景资源定位地址。 - /// 是否主场景。 public bool IsMainScene(string location) { - // 获取当前激活的场景 - UnityEngine.SceneManagement.Scene currentScene = SceneManager.GetActiveScene(); + var sceneState = EnsureSceneState(); + var currentScene = SceneManager.GetActiveScene(); - if (_currentMainSceneName.Equals(location)) + if (sceneState.CurrentMainSceneName.Equals(location)) { - if (_currentMainScene == null) + if (sceneState.CurrentMainSceneHandle == null) { - return false; + return currentScene.name == location; } - // 判断当前场景是否是主场景 - if (currentScene.name == _currentMainScene.SceneName) + if (currentScene.name == sceneState.CurrentMainSceneHandle.SceneName) { return true; } - return _currentMainScene.SceneName == currentScene.name; + return sceneState.CurrentMainSceneHandle.SceneName == currentScene.name; } - // 判断当前场景是否是主场景 - if (currentScene.name == _currentMainScene?.SceneName) + if (currentScene.name == sceneState.CurrentMainSceneHandle?.SceneName) { return true; } @@ -300,15 +206,10 @@ namespace AlicizaX.Scene.Runtime return false; } - /// - /// 异步卸载子场景。 - /// - /// 场景资源定位地址。 - /// 进度回调。 public async UniTask UnloadAsync(string location, Action progressCallBack = null) { - _subScenes.TryGetValue(location, out SceneHandle subScene); - if (subScene != null) + var sceneState = EnsureSceneState(); + if (sceneState.TryGetSubScene(location, out var subScene) && subScene != null) { if (subScene.SceneObject == default) { @@ -316,14 +217,13 @@ namespace AlicizaX.Scene.Runtime return false; } - if (!_handlingScene.Add(location)) + if (!sceneState.TryBeginHandling(location)) { Log.Warning($"Could not unload Scene while loading. Scene: {location}"); return false; } var unloadOperation = subScene.UnloadAsync(); - if (progressCallBack != null) { while (!unloadOperation.IsDone && unloadOperation.Status != EOperationStatus.Failed) @@ -337,10 +237,8 @@ namespace AlicizaX.Scene.Runtime await unloadOperation.ToUniTask(); } - _subScenes.Remove(location); - - _handlingScene.Remove(location); - + sceneState.RemoveSubScene(location); + sceneState.EndHandling(location); return true; } @@ -348,16 +246,10 @@ namespace AlicizaX.Scene.Runtime return false; } - /// - /// 异步卸载子场景。 - /// - /// 场景资源定位地址。 - /// 卸载完成回调。 - /// 进度回调。 public void Unload(string location, Action callBack = null, Action progressCallBack = null) { - _subScenes.TryGetValue(location, out SceneHandle subScene); - if (subScene != null) + var sceneState = EnsureSceneState(); + if (sceneState.TryGetSubScene(location, out var subScene) && subScene != null) { if (subScene.SceneObject == default) { @@ -365,23 +257,23 @@ namespace AlicizaX.Scene.Runtime return; } - if (!_handlingScene.Add(location)) + if (!sceneState.TryBeginHandling(location)) { Log.Warning($"Could not unload Scene while loading. Scene: {location}"); return; } - subScene.UnloadAsync(); - subScene.UnloadAsync().Completed += @base => + var unloadOperation = subScene.UnloadAsync(); + unloadOperation.Completed += @base => { - _subScenes.Remove(location); - _handlingScene.Remove(location); + sceneState.RemoveSubScene(location); + sceneState.EndHandling(location); callBack?.Invoke(); }; if (progressCallBack != null) { - InvokeProgress(subScene, progressCallBack).Forget(); + InvokeOperationProgress(unloadOperation, progressCallBack).Forget(); } return; @@ -390,19 +282,140 @@ namespace AlicizaX.Scene.Runtime Log.Warning($"UnloadAsync invalid location:{location}"); } - /// - /// 是否包含场景。 - /// - /// 场景资源定位地址。 - /// 是否包含场景。 public bool IsContainScene(string location) { - if (_currentMainSceneName.Equals(location)) + return EnsureSceneState().IsContainScene(location); + } + + private SceneDomainStateService EnsureSceneState() + { + var sceneScope = Context.EnsureScene(); + if (!sceneScope.TryGet(out var sceneState)) + { + sceneState = sceneScope.Register(new SceneDomainStateService()); + } + + return sceneState; + } + + private SceneDomainStateService PrepareSceneStateForMainSceneLoad(string location) + { + var sceneScope = Context.ResetScene(); + var sceneState = sceneScope.Register(new SceneDomainStateService()); + sceneState.MarkMainSceneLoading(location); + return sceneState; + } + + private async UniTaskVoid InvokeSceneProgress(SceneHandle sceneHandle, Action progress) + { + if (sceneHandle == null) + { + return; + } + + while (!sceneHandle.IsDone && sceneHandle.IsValid) + { + await UniTask.Yield(); + progress?.Invoke(sceneHandle.Progress); + } + } + + private async UniTaskVoid InvokeOperationProgress(AsyncOperationBase operation, Action progress) + { + if (operation == null) + { + return; + } + + while (!operation.IsDone && operation.Status != EOperationStatus.Failed) + { + await UniTask.Yield(); + progress?.Invoke(operation.Progress); + } + } + } + + internal sealed class SceneDomainStateService : ServiceBase, ISceneStateService + { + private readonly Dictionary _subScenes = new Dictionary(); + private readonly HashSet _handlingScenes = new HashSet(); + + public string CurrentMainSceneName { get; private set; } = string.Empty; + + public SceneHandle CurrentMainSceneHandle { get; private set; } + + protected override void OnInitialize() + { + } + + protected override void OnDestroyService() + { + foreach (var subScene in _subScenes.Values) + { + subScene?.UnloadAsync(); + } + + _subScenes.Clear(); + _handlingScenes.Clear(); + CurrentMainSceneHandle = null; + CurrentMainSceneName = string.Empty; + } + + public void SetBootScene(string sceneName) + { + CurrentMainSceneName = sceneName ?? string.Empty; + CurrentMainSceneHandle = null; + _subScenes.Clear(); + _handlingScenes.Clear(); + } + + public void MarkMainSceneLoading(string sceneName) + { + CurrentMainSceneName = sceneName ?? string.Empty; + CurrentMainSceneHandle = null; + _handlingScenes.Clear(); + TryBeginHandling(sceneName); + } + + public void SetMainScene(string sceneName, SceneHandle sceneHandle) + { + CurrentMainSceneName = sceneName ?? string.Empty; + CurrentMainSceneHandle = sceneHandle; + } + + public bool TryBeginHandling(string location) + => !string.IsNullOrEmpty(location) && _handlingScenes.Add(location); + + public void EndHandling(string location) + { + if (!string.IsNullOrEmpty(location)) + { + _handlingScenes.Remove(location); + } + } + + public void AddSubScene(string location, SceneHandle sceneHandle) + { + _subScenes[location] = sceneHandle; + } + + public bool TryGetSubScene(string location, out SceneHandle sceneHandle) + => _subScenes.TryGetValue(location, out sceneHandle); + + public bool RemoveSubScene(string location) + => _subScenes.Remove(location); + + public bool IsContainScene(string location) + { + if (CurrentMainSceneName.Equals(location)) { return true; } - return _subScenes.TryGetValue(location, out var _); + return _subScenes.ContainsKey(location); } + + public bool IsMainScene(string location) + => CurrentMainSceneName.Equals(location); } } diff --git a/Runtime/Timer/TimerComponent.cs b/Runtime/Timer/TimerComponent.cs index 4fb4390..9155008 100644 --- a/Runtime/Timer/TimerComponent.cs +++ b/Runtime/Timer/TimerComponent.cs @@ -10,7 +10,8 @@ namespace AlicizaX.Timer.Runtime { private void Awake() { - AppServices.GetOrCreateScope().Register(new TimerService()); + AppServices.App.Register(new TimerService()); } } } + diff --git a/Runtime/UI/Constant/UIHolderFactory.cs b/Runtime/UI/Constant/UIHolderFactory.cs index 8799b0a..d422ab5 100644 --- a/Runtime/UI/Constant/UIHolderFactory.cs +++ b/Runtime/UI/Constant/UIHolderFactory.cs @@ -11,6 +11,7 @@ namespace AlicizaX.UI.Runtime public static class UIHolderFactory { private static IResourceService ResourceService => AppServices.Require(); + private static bool AllowLegacyResourcesFallback => UnityEngine.Application.isEditor || UnityEngine.Debug.isDebugBuild; public static async UniTask CreateUIHolderAsync(Transform parent) where T : UIHolderObjectBase { @@ -41,14 +42,14 @@ namespace AlicizaX.UI.Runtime { return resInfo.LoadType == EUIResLoadType.AssetBundle ? await ResourceService.LoadGameObjectAsync(resInfo.Location, parent) - : await InstantiateResourceAsync(resInfo.Location, parent); + : await LoadResourceWithFallbackAsync(resInfo.Location, parent); } internal static GameObject LoadUIResourcesSync(UIResRegistry.UIResInfo resInfo, Transform parent) { return resInfo.LoadType == EUIResLoadType.AssetBundle ? ResourceService.LoadGameObject(resInfo.Location, parent) - : InstantiateResourceSync(resInfo.Location, parent); + : LoadResourceWithFallbackSync(resInfo.Location, parent); } @@ -88,6 +89,58 @@ namespace AlicizaX.UI.Runtime return Object.Instantiate(prefab, parent); } + private static async UniTask LoadResourceWithFallbackAsync(string location, Transform parent) + { + try + { + var managedObject = await ResourceService.LoadGameObjectAsync(location, parent); + if (managedObject != null) + { + return managedObject; + } + } + catch + { + if (!AllowLegacyResourcesFallback) + { + throw; + } + } + + if (!AllowLegacyResourcesFallback) + { + throw new NullReferenceException($"UI resource load failed via IResourceService: {location}"); + } + + return await InstantiateResourceAsync(location, parent); + } + + private static GameObject LoadResourceWithFallbackSync(string location, Transform parent) + { + try + { + var managedObject = ResourceService.LoadGameObject(location, parent); + if (managedObject != null) + { + return managedObject; + } + } + catch + { + if (!AllowLegacyResourcesFallback) + { + throw; + } + } + + if (!AllowLegacyResourcesFallback) + { + throw new NullReferenceException($"UI resource load failed via IResourceService: {location}"); + } + + return InstantiateResourceSync(location, parent); + } + private static void ValidateAndBind(UIMetadata meta, GameObject holderObject, UIBase owner) { if (!holderObject) throw new NullReferenceException($"UI resource load failed: {meta.ResInfo.Location}"); diff --git a/Runtime/UI/Constant/UIMetadataFactory.cs b/Runtime/UI/Constant/UIMetadataFactory.cs index 90f9905..a7cfb27 100644 --- a/Runtime/UI/Constant/UIMetadataFactory.cs +++ b/Runtime/UI/Constant/UIMetadataFactory.cs @@ -13,7 +13,14 @@ namespace AlicizaX.UI.Runtime static UIMetadataFactory() { - m_UIMetadataPool = AppServices.Require().CreateSingleSpawnObjectPool("UI Metadata Pool", 60, 16, 60f, 0); + m_UIMetadataPool = AppServices.Require().CreatePool( + new ObjectPoolCreateOptions( + name: "UI Metadata Pool", + allowMultiSpawn: false, + autoReleaseInterval: 60, + capacity: 16, + expireTime: 60f, + priority: 0)); } internal static UIMetadata GetWindowMetadata() diff --git a/Runtime/UI/Manager/UIService.Open.cs b/Runtime/UI/Manager/UIService.Open.cs index 853092d..a8bdf08 100644 --- a/Runtime/UI/Manager/UIService.Open.cs +++ b/Runtime/UI/Manager/UIService.Open.cs @@ -7,17 +7,17 @@ using Cysharp.Threading.Tasks; namespace AlicizaX.UI.Runtime { - readonly struct LayerData + sealed class LayerData { - public readonly List OrderList; // 维护插入顺序 - public readonly HashSet HandleSet; // O(1)存在性检查 - public readonly Dictionary IndexMap; // O(1)索引查找 + public readonly List OrderList; + public readonly Dictionary IndexMap; + public int LastFullscreenIndex; public LayerData(int initialCapacity) { OrderList = new List(initialCapacity); - HandleSet = new HashSet(); IndexMap = new Dictionary(initialCapacity); + LastFullscreenIndex = -1; } } @@ -70,9 +70,9 @@ namespace AlicizaX.UI.Runtime if (meta.State == UIState.Loaded || meta.State == UIState.Initialized) { meta.CancelAsyncOperations(); - Pop(meta); - SortWindowVisible(meta.MetaInfo.UILayer); - SortWindowDepth(meta.MetaInfo.UILayer); + var popResult = Pop(meta); + SortWindowVisible(meta.MetaInfo.UILayer, popResult.previousFullscreenIndex); + SortWindowDepth(meta.MetaInfo.UILayer, popResult.removedIndex >= 0 ? popResult.removedIndex : 0); meta.View.Visible = false; CacheWindow(meta, force); return; @@ -85,9 +85,9 @@ namespace AlicizaX.UI.Runtime return; } - Pop(meta); - SortWindowVisible(meta.MetaInfo.UILayer); - SortWindowDepth(meta.MetaInfo.UILayer); + var closedPopResult = Pop(meta); + SortWindowVisible(meta.MetaInfo.UILayer, closedPopResult.previousFullscreenIndex); + SortWindowDepth(meta.MetaInfo.UILayer, closedPopResult.removedIndex >= 0 ? closedPopResult.removedIndex : 0); CacheWindow(meta, force); } @@ -132,35 +132,42 @@ namespace AlicizaX.UI.Runtime [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Push(UIMetadata meta) { - ref var layer = ref _openUI[meta.MetaInfo.UILayer]; - if (layer.HandleSet.Add(meta.MetaInfo.RuntimeTypeHandle)) + var layer = _openUI[meta.MetaInfo.UILayer]; + if (!layer.IndexMap.ContainsKey(meta.MetaInfo.RuntimeTypeHandle)) { int index = layer.OrderList.Count; layer.OrderList.Add(meta); layer.IndexMap[meta.MetaInfo.RuntimeTypeHandle] = index; + if (meta.MetaInfo.FullScreen) + { + layer.LastFullscreenIndex = index; + } + UpdateLayerParent(meta); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Pop(UIMetadata meta) + private (int removedIndex, int previousFullscreenIndex) Pop(UIMetadata meta) { - ref var layer = ref _openUI[meta.MetaInfo.UILayer]; - if (layer.HandleSet.Remove(meta.MetaInfo.RuntimeTypeHandle)) + var layer = _openUI[meta.MetaInfo.UILayer]; + int previousFullscreenIndex = layer.LastFullscreenIndex; + if (layer.IndexMap.TryGetValue(meta.MetaInfo.RuntimeTypeHandle, out int index)) { - if (layer.IndexMap.TryGetValue(meta.MetaInfo.RuntimeTypeHandle, out int index)) - { - layer.OrderList.RemoveAt(index); - layer.IndexMap.Remove(meta.MetaInfo.RuntimeTypeHandle); + layer.OrderList.RemoveAt(index); + layer.IndexMap.Remove(meta.MetaInfo.RuntimeTypeHandle); - // Update indices for all elements after the removed one - for (int i = index; i < layer.OrderList.Count; i++) - { - var m = layer.OrderList[i]; - layer.IndexMap[m.MetaInfo.RuntimeTypeHandle] = i; - } + for (int i = index; i < layer.OrderList.Count; i++) + { + var item = layer.OrderList[i]; + layer.IndexMap[item.MetaInfo.RuntimeTypeHandle] = i; } + + UpdateFullscreenIndexAfterRemove(layer, meta, index); + return (index, previousFullscreenIndex); } + + return (-1, previousFullscreenIndex); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -176,7 +183,7 @@ namespace AlicizaX.UI.Runtime [MethodImpl(MethodImplOptions.AggressiveInlining)] private void MoveToTop(UIMetadata meta) { - ref var layer = ref _openUI[meta.MetaInfo.UILayer]; + var layer = _openUI[meta.MetaInfo.UILayer]; int lastIdx = layer.OrderList.Count - 1; if (!layer.IndexMap.TryGetValue(meta.MetaInfo.RuntimeTypeHandle, out int currentIdx)) @@ -189,11 +196,12 @@ namespace AlicizaX.UI.Runtime for (int i = currentIdx; i < lastIdx; i++) { - var m = layer.OrderList[i]; - layer.IndexMap[m.MetaInfo.RuntimeTypeHandle] = i; + var item = layer.OrderList[i]; + layer.IndexMap[item.MetaInfo.RuntimeTypeHandle] = i; } layer.IndexMap[meta.MetaInfo.RuntimeTypeHandle] = lastIdx; + UpdateFullscreenIndexAfterMove(layer, meta, currentIdx, lastIdx); SortWindowDepth(meta.MetaInfo.UILayer, currentIdx); } @@ -212,36 +220,40 @@ namespace AlicizaX.UI.Runtime await meta.View.InternalOpen(cancellationToken); } - private void SortWindowVisible(int layer) + private void SortWindowVisible(int layer, int previousFullscreenIndex = int.MinValue) { - var list = _openUI[layer].OrderList; + var layerData = _openUI[layer]; + var list = layerData.OrderList; int count = list.Count; - int fullscreenIdx = -1; - for (int i = count - 1; i >= 0; i--) + int fullscreenIdx = layerData.LastFullscreenIndex; + if (fullscreenIdx >= count || (fullscreenIdx >= 0 && !IsDisplayFullscreen(list[fullscreenIdx]))) { - var meta = list[i]; - if (meta.MetaInfo.FullScreen && UIStateMachine.IsDisplayActive(meta.State)) - { - fullscreenIdx = i; - break; - } + fullscreenIdx = FindLastFullscreenIndex(list, count - 1); + layerData.LastFullscreenIndex = fullscreenIdx; } - if (fullscreenIdx == -1) + int oldFullscreenIndex = previousFullscreenIndex == int.MinValue ? fullscreenIdx : previousFullscreenIndex; + if (oldFullscreenIndex == fullscreenIdx) { - for (int i = 0; i < count; i++) - { - list[i].View.Visible = true; - } + ApplyVisibilityRange(list, fullscreenIdx >= 0 ? fullscreenIdx : 0, count, fullscreenIdx); + return; } - else + + if (oldFullscreenIndex == -1 && fullscreenIdx == -1) { - for (int i = 0; i < count; i++) - { - list[i].View.Visible = (i >= fullscreenIdx); - } + ApplyVisibilityRange(list, 0, count, -1); + return; } + + int start = oldFullscreenIndex < 0 || fullscreenIdx < 0 + ? 0 + : Math.Min(oldFullscreenIndex, fullscreenIdx); + int endExclusive = oldFullscreenIndex < 0 || fullscreenIdx < 0 + ? count + : Math.Max(oldFullscreenIndex, fullscreenIdx) + 1; + + ApplyVisibilityRange(list, start, endExclusive, fullscreenIdx); } private void SortWindowDepth(int layer, int startIndex = 0) @@ -259,5 +271,83 @@ namespace AlicizaX.UI.Runtime } } } + + private static bool IsDisplayFullscreen(UIMetadata meta) + { + return meta.MetaInfo.FullScreen && UIStateMachine.IsDisplayActive(meta.State); + } + + private static int FindLastFullscreenIndex(List list, int startIndex) + { + for (int i = Math.Min(startIndex, list.Count - 1); i >= 0; i--) + { + if (IsDisplayFullscreen(list[i])) + { + return i; + } + } + + return -1; + } + + private static void ApplyVisibilityRange(List list, int startInclusive, int endExclusive, int fullscreenIdx) + { + if (startInclusive < 0) + { + startInclusive = 0; + } + + if (endExclusive > list.Count) + { + endExclusive = list.Count; + } + + bool showAll = fullscreenIdx < 0; + for (int i = startInclusive; i < endExclusive; i++) + { + list[i].View.Visible = showAll || i >= fullscreenIdx; + } + } + + private static void UpdateFullscreenIndexAfterRemove(LayerData layer, UIMetadata removedMeta, int removedIndex) + { + if (layer.OrderList.Count == 0) + { + layer.LastFullscreenIndex = -1; + return; + } + + if (removedMeta.MetaInfo.FullScreen && layer.LastFullscreenIndex == removedIndex) + { + layer.LastFullscreenIndex = FindLastFullscreenIndex(layer.OrderList, removedIndex - 1); + return; + } + + if (removedIndex < layer.LastFullscreenIndex) + { + layer.LastFullscreenIndex--; + } + } + + private static void UpdateFullscreenIndexAfterMove(LayerData layer, UIMetadata meta, int fromIndex, int toIndex) + { + if (layer.LastFullscreenIndex == fromIndex) + { + layer.LastFullscreenIndex = toIndex; + return; + } + + if (!meta.MetaInfo.FullScreen) + { + return; + } + + if (fromIndex < layer.LastFullscreenIndex) + { + layer.LastFullscreenIndex--; + } + + layer.LastFullscreenIndex = Math.Max(layer.LastFullscreenIndex, toIndex); + } } } diff --git a/Runtime/UI/UIBase/UITabWindow.cs b/Runtime/UI/UIBase/UITabWindow.cs index 7028caa..93a62bc 100644 --- a/Runtime/UI/UIBase/UITabWindow.cs +++ b/Runtime/UI/UIBase/UITabWindow.cs @@ -40,7 +40,10 @@ namespace AlicizaX.UI.Runtime Holder = holder; _canvas = Holder.transform.GetComponent(); - _canvas.overrideSorting = true; + if (_canvas != null) + { + _canvas.overrideSorting = owner == null; + } _raycaster = Holder.transform.GetComponent(); Holder.RectTransform.localPosition = Vector3.zero; Holder.RectTransform.pivot = new Vector2(0.5f, 0.5f); @@ -96,7 +99,7 @@ namespace AlicizaX.UI.Runtime StartAsyncLoading(typeHandle, userDatas).Forget(); } - private async UniTaskVoid StartAsyncLoading(RuntimeTypeHandle typeHandle, params System.Object[] userDatas) + private async UniTask StartAsyncLoading(RuntimeTypeHandle typeHandle, params System.Object[] userDatas) { _loadingFlags[typeHandle] = true; diff --git a/Runtime/UI/UIBase/UIWidget.cs b/Runtime/UI/UIBase/UIWidget.cs index 0c55cf5..7ea97cd 100644 --- a/Runtime/UI/UIBase/UIWidget.cs +++ b/Runtime/UI/UIBase/UIWidget.cs @@ -40,6 +40,10 @@ namespace AlicizaX.UI.Runtime Holder = holder; _parent = owner; _canvas = Holder.transform.GetComponent(); + if (_canvas != null) + { + _canvas.overrideSorting = false; + } _raycaster = Holder.transform.GetComponent(); Depth = owner.Depth + 5; _state = UIState.Loaded; diff --git a/Runtime/UI/UIBase/UIWindow.cs b/Runtime/UI/UIBase/UIWindow.cs index 2b03088..45224af 100644 --- a/Runtime/UI/UIBase/UIWindow.cs +++ b/Runtime/UI/UIBase/UIWindow.cs @@ -31,7 +31,10 @@ namespace AlicizaX.UI.Runtime throw new InvalidOperationException("UI already Created"); Holder = holder; _canvas = Holder.transform.GetComponent(); - _canvas.overrideSorting = true; + if (_canvas != null) + { + _canvas.overrideSorting = owner == null; + } _raycaster = Holder.transform.GetComponent(); Holder.RectTransform.localPosition = Vector3.zero; Holder.RectTransform.pivot = new Vector2(0.5f, 0.5f); diff --git a/Runtime/UI/UIComponent.cs b/Runtime/UI/UIComponent.cs index 10e8aab..46a9491 100644 --- a/Runtime/UI/UIComponent.cs +++ b/Runtime/UI/UIComponent.cs @@ -29,16 +29,17 @@ namespace AlicizaX.UI.Runtime private void Awake() { - _uiService = AppServices.GetOrCreateScope().Register(new UIService()); + _uiService = AppServices.App.Register(new UIService()); if (uiRoot == null) { throw new GameFrameworkException("UIRoot Prefab is invalid."); } + GameObject obj = Instantiate(uiRoot, Vector3.zero, Quaternion.identity); obj.name = "------UI Root------"; _instanceRoot = obj.transform; Object.DontDestroyOnLoad(_instanceRoot); - _uiService.Initialize(_instanceRoot,_isOrthographic); + _uiService.Initialize(_instanceRoot, _isOrthographic); } private void Start() @@ -50,9 +51,9 @@ namespace AlicizaX.UI.Runtime #region 设置安全区域 /// - /// 设置屏幕安全区域(异形屏支持)。 + /// 应用屏幕安全区域,适配刘海屏等显示区域。 /// - /// 安全区域 + /// 安全区域。 public void ApplyScreenSafeRect(Rect safeRect) { CanvasScaler scaler = _uiService.UICanvasRoot.GetComponent(); @@ -73,31 +74,31 @@ namespace AlicizaX.UI.Runtime float offsetMaxX = scaler.referenceResolution.x - width - posX; float offsetMaxY = scaler.referenceResolution.y - height - posY; - // 注意:安全区坐标系的原点为左下角 + // 注意:安全区域坐标系原点为左下角。 var rectTrans = _uiService.UICanvasRoot.transform as RectTransform; if (rectTrans != null) { - rectTrans.offsetMin = new Vector2(posX, posY); //锚框状态下的屏幕左下角偏移向量 - rectTrans.offsetMax = new Vector2(-offsetMaxX, -offsetMaxY); //锚框状态下的屏幕右上角偏移向量 + rectTrans.offsetMin = new Vector2(posX, posY); // 锚点状态下的屏幕左下角偏移量。 + rectTrans.offsetMax = new Vector2(-offsetMaxX, -offsetMaxY); // 锚点状态下的屏幕右上角偏移量。 } } /// - /// 模拟IPhoneX异形屏 + /// ģ��IPhoneX������ /// public void SimulateIPhoneXNotchScreen() { Rect rect; if (Screen.height > Screen.width) { - // 竖屏Portrait + // 竖屏 float deviceWidth = 1125; float deviceHeight = 2436; rect = new Rect(0f / deviceWidth, 102f / deviceHeight, 1125f / deviceWidth, 2202f / deviceHeight); } else { - // 横屏Landscape + // 横屏 float deviceWidth = 2436; float deviceHeight = 1125; rect = new Rect(132f / deviceWidth, 63f / deviceHeight, 2172f / deviceWidth, 1062f / deviceHeight);