From 1dcf309b78c0ea9c30b5df3d2084a0666d9e1567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Mon, 9 Mar 2026 20:13:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ui=E5=BC=82=E6=AD=A5=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E4=BB=A4=E7=89=8C=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=BF=AB?= =?UTF-8?q?=E9=80=9F=E6=89=93=E5=BC=80=E7=9A=84=E7=A8=B3=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runtime/UI/Constant/UIMetaRegistry.cs | 6 - Runtime/UI/Constant/UIMetadata.cs | 14 + Runtime/UI/Constant/UIMetadataObject.cs | 1 - Runtime/UI/Manager/UIModule.Cache.cs | 24 +- Runtime/UI/Manager/UIModule.Open.cs | 10 +- Runtime/UI/OPTIMIZATION_SUMMARY.md | 275 --------------- Runtime/UI/OPTIMIZATION_SUMMARY.md.meta | 7 - Runtime/UI/PHASE1_OPTIMIZATIONS.md | 236 ------------- Runtime/UI/PHASE1_OPTIMIZATIONS.md.meta | 7 - Runtime/UI/PHASE2_OPTIMIZATIONS.md | 434 ------------------------ Runtime/UI/PHASE2_OPTIMIZATIONS.md.meta | 7 - Runtime/UI/PHASE3_OPTIMIZATIONS.md | 431 ----------------------- Runtime/UI/PHASE3_OPTIMIZATIONS.md.meta | 7 - Runtime/UI/UIBase/UIBase.Widget.cs | 12 +- Runtime/UI/UIBase/UIBase.cs | 19 +- Runtime/UI/UIBase/UIStateMachine.cs | 27 +- 16 files changed, 57 insertions(+), 1460 deletions(-) delete mode 100644 Runtime/UI/OPTIMIZATION_SUMMARY.md delete mode 100644 Runtime/UI/OPTIMIZATION_SUMMARY.md.meta delete mode 100644 Runtime/UI/PHASE1_OPTIMIZATIONS.md delete mode 100644 Runtime/UI/PHASE1_OPTIMIZATIONS.md.meta delete mode 100644 Runtime/UI/PHASE2_OPTIMIZATIONS.md delete mode 100644 Runtime/UI/PHASE2_OPTIMIZATIONS.md.meta delete mode 100644 Runtime/UI/PHASE3_OPTIMIZATIONS.md delete mode 100644 Runtime/UI/PHASE3_OPTIMIZATIONS.md.meta diff --git a/Runtime/UI/Constant/UIMetaRegistry.cs b/Runtime/UI/Constant/UIMetaRegistry.cs index 4a9c85b..d3fcd82 100644 --- a/Runtime/UI/Constant/UIMetaRegistry.cs +++ b/Runtime/UI/Constant/UIMetaRegistry.cs @@ -81,10 +81,6 @@ namespace AlicizaX.UI.Runtime return TryReflectAndRegisterInternal(uiType, out info); } - /// - /// Internal method to reflect and register UI type without logging. - /// Used by both runtime fallback and pre-registration. - /// [MethodImpl(MethodImplOptions.NoInlining)] private static bool TryReflectAndRegisterInternal(Type uiType, out UIMetaInfo info) { @@ -93,7 +89,6 @@ namespace AlicizaX.UI.Runtime Type baseType = uiType; Type? holderType = null; - // Get holder type from generic arguments var genericArgs = baseType.GetGenericArguments(); if (genericArgs.Length > 0) { @@ -105,7 +100,6 @@ namespace AlicizaX.UI.Runtime int cacheTime = 0; bool needUpdate = false; - // Read attributes var windowAttribute = CustomAttributeData.GetCustomAttributes(uiType) .FirstOrDefault(a => a.AttributeType.Name == nameof(WindowAttribute)); var uiUpdateAttribute = CustomAttributeData.GetCustomAttributes(uiType) diff --git a/Runtime/UI/Constant/UIMetadata.cs b/Runtime/UI/Constant/UIMetadata.cs index 0879e04..a408447 100644 --- a/Runtime/UI/Constant/UIMetadata.cs +++ b/Runtime/UI/Constant/UIMetadata.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using System.Threading; using AlicizaX; using Cysharp.Threading.Tasks; @@ -13,6 +14,9 @@ namespace AlicizaX.UI.Runtime public readonly Type UILogicType; public bool InCache = false; + private CancellationTokenSource _cancellationTokenSource; + public CancellationToken CancellationToken => _cancellationTokenSource?.Token ?? CancellationToken.None; + public UIState State { get @@ -31,9 +35,17 @@ namespace AlicizaX.UI.Runtime return; View = (UIBase)InstanceFactory.CreateInstanceOptimized(UILogicType); + _cancellationTokenSource = new CancellationTokenSource(); } } + public void CancelAsyncOperations() + { + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } + public void Dispose() { DisposeAsync().Forget(); @@ -41,6 +53,8 @@ namespace AlicizaX.UI.Runtime private async UniTask DisposeAsync() { + CancelAsyncOperations(); + if (State != UIState.Uninitialized && State != UIState.Destroying) { await View.InternalDestroy(); diff --git a/Runtime/UI/Constant/UIMetadataObject.cs b/Runtime/UI/Constant/UIMetadataObject.cs index d7b89d6..e39f14c 100644 --- a/Runtime/UI/Constant/UIMetadataObject.cs +++ b/Runtime/UI/Constant/UIMetadataObject.cs @@ -15,7 +15,6 @@ namespace AlicizaX.UI.Runtime public static UIMetadataObject Create(UIMetadata target, RuntimeTypeHandle handle) { UIMetadataObject obj = MemoryPool.Acquire(); - // Use type handle hash code as name to avoid string allocation obj.Initialize(handle.GetHashCode().ToString(), target); return obj; } diff --git a/Runtime/UI/Manager/UIModule.Cache.cs b/Runtime/UI/Manager/UIModule.Cache.cs index 62fcbdb..7adf841 100644 --- a/Runtime/UI/Manager/UIModule.Cache.cs +++ b/Runtime/UI/Manager/UIModule.Cache.cs @@ -6,7 +6,19 @@ namespace AlicizaX.UI.Runtime { internal sealed partial class UIModule { - private readonly Dictionary m_CacheWindow = new(); + private readonly struct CacheEntry + { + public readonly UIMetadata Metadata; + public readonly int TimerId; + + public CacheEntry(UIMetadata metadata, int timerId) + { + Metadata = metadata; + TimerId = timerId; + } + } + + private readonly Dictionary m_CacheWindow = new(); private void CacheWindow(UIMetadata uiMetadata, bool force) { @@ -43,7 +55,7 @@ namespace AlicizaX.UI.Runtime } uiMetadata.InCache = true; - m_CacheWindow.Add(uiMetadata.MetaInfo.RuntimeTypeHandle, (uiMetadata, timerId)); + m_CacheWindow.Add(uiMetadata.MetaInfo.RuntimeTypeHandle, new CacheEntry(uiMetadata, timerId)); } private void OnTimerDisposeWindow(object[] args) @@ -60,13 +72,13 @@ namespace AlicizaX.UI.Runtime private void RemoveFromCache(RuntimeTypeHandle typeHandle) { - if (m_CacheWindow.TryGetValue(typeHandle, out var result)) + if (m_CacheWindow.TryGetValue(typeHandle, out var entry)) { m_CacheWindow.Remove(typeHandle); - result.Item1.InCache = false; - if (result.Item2 > 0) + entry.Metadata.InCache = false; + if (entry.TimerId > 0) { - _timerModule.RemoveTimer(result.Item2); + _timerModule.RemoveTimer(entry.TimerId); } } } diff --git a/Runtime/UI/Manager/UIModule.Open.cs b/Runtime/UI/Manager/UIModule.Open.cs index fcee69e..379fdbb 100644 --- a/Runtime/UI/Manager/UIModule.Open.cs +++ b/Runtime/UI/Manager/UIModule.Open.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Threading; using AlicizaX; using Cysharp.Threading.Tasks; @@ -29,7 +30,7 @@ namespace AlicizaX.UI.Runtime CreateMetaUI(metaInfo); await UIHolderFactory.CreateUIResourceAsync(metaInfo, UICacheLayer); FinalizeShow(metaInfo, userDatas); - UpdateVisualState(metaInfo).Forget(); + await UpdateVisualState(metaInfo, metaInfo.CancellationToken); return metaInfo.View; } @@ -49,6 +50,7 @@ namespace AlicizaX.UI.Runtime return; } + meta.CancelAsyncOperations(); await meta.View.InternalClose(); Pop(meta); SortWindowVisible(meta.MetaInfo.UILayer); @@ -167,16 +169,16 @@ namespace AlicizaX.UI.Runtime } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private async UniTask UpdateVisualState(UIMetadata meta) + private async UniTask UpdateVisualState(UIMetadata meta, CancellationToken cancellationToken = default) { SortWindowVisible(meta.MetaInfo.UILayer); SortWindowDepth(meta.MetaInfo.UILayer); if (meta.State == UIState.Loaded) { - await meta.View.InternalInitlized(); + await meta.View.InternalInitlized(cancellationToken); } - await meta.View.InternalOpen(); + await meta.View.InternalOpen(cancellationToken); } private void SortWindowVisible(int layer) diff --git a/Runtime/UI/OPTIMIZATION_SUMMARY.md b/Runtime/UI/OPTIMIZATION_SUMMARY.md deleted file mode 100644 index f5c36b2..0000000 --- a/Runtime/UI/OPTIMIZATION_SUMMARY.md +++ /dev/null @@ -1,275 +0,0 @@ -# UI Module Optimization - Complete Summary - -## Date: 2025-12-24 - -## Executive Summary - -Successfully implemented comprehensive optimizations across 3 phases for the Aliciza X Unity UI module, resulting in significant performance improvements, reduced memory allocations, and better code quality. - ---- - -## Overall Performance Improvements - -### Memory Allocations Eliminated - -| Operation | Before | After | Savings | -|-----------|--------|-------|---------| -| **First UI Open** | 5-10ms reflection | 0ms | **5-10ms** | -| **UI Open (GC)** | ~150 bytes | 0 bytes | **150 bytes** | -| **Widget Update (per frame)** | 56 bytes | 0 bytes | **56 bytes** | -| **Widget Creation** | 40 bytes | 0 bytes | **40 bytes** | -| **Window Switch** | O(n) + 1-2ms | O(1) + 0ms | **1-2ms** | - -### Total Impact Per Session -- **Startup:** One-time 10-30ms cost for pre-registration (acceptable trade-off) -- **Per UI Open:** ~200 bytes GC eliminated -- **Per Frame (with widgets):** ~100 bytes GC eliminated -- **Window Operations:** 50-70% faster - ---- - -## Phase 1: Critical Optimizations ✅ - -### 1. Pre-Register All UI Types at Startup -- **Problem:** 5-10ms reflection overhead on first UI open -- **Solution:** Automatic pre-registration at startup using `[RuntimeInitializeOnLoadMethod]` -- **Impact:** Eliminate all runtime reflection overhead -- **Files:** `UIMetaRegistry.cs`, `UIResRegistry.cs` - -### 2. Replace List.IndexOf with Index Tracking -- **Problem:** O(n) linear search in `MoveToTop()` -- **Solution:** Added `IndexMap` dictionary for O(1) lookups -- **Impact:** 0.5-2ms faster window switching -- **Files:** `UIModule.Open.cs` - -### 3. Fix Async State Machine Allocations -- **Problem:** Unnecessary async state machine allocations -- **Solution:** Return `UniTask.CompletedTask` instead of `await` -- **Impact:** Eliminate ~150 bytes per UI open -- **Files:** `UIBase.cs` - ---- - -## Phase 2: High Priority Optimizations ✅ - -### 4. Widget Dictionary Enumeration -- **Problem:** 40-56 bytes allocation per frame from dictionary enumeration -- **Solution:** Cache updateable widgets in list, use struct enumerator -- **Impact:** Zero allocation per frame -- **Files:** `UIBase.Widget.cs` - -### 5. UIMetadataFactory String Allocations -- **Problem:** String allocation on every widget creation -- **Solution:** Use `RuntimeTypeHandle` as key instead of string -- **Impact:** Eliminate 40+ bytes per widget -- **Files:** `UIMetadataFactory.cs`, `UIMetadataObject.cs` - -### 6. SortWindowVisible Early Exit -- **Problem:** No early exit when fullscreen found -- **Solution:** Two-phase algorithm with early exit -- **Impact:** Faster visibility sorting -- **Files:** `UIModule.Open.cs` - -### 7. Cache Timer Management -- **Problem:** Typos and poor error handling -- **Solution:** Fixed typos, added validation, better error messages -- **Impact:** Better code quality and reliability -- **Files:** `UIModule.Cache.cs` - ---- - -## Phase 3: Medium Priority Optimizations ✅ - -### 8. UI State Machine Validation -- **Problem:** No validation of state transitions -- **Solution:** Created `UIStateMachine` with full validation -- **Impact:** Better debugging, prevent crashes -- **Files:** `UIStateMachine.cs` (new), `UIBase.cs`, `UIMetadata.cs` - -### 9. Depth Sorting Optimization -- **Problem:** Recalculate depth for all windows -- **Solution:** Only update changed windows, check before setting -- **Impact:** Fewer Canvas updates -- **Files:** `UIModule.Open.cs` - -### 10. Remove Implicit Bool Operator -- **Problem:** Confusing implicit operator overload -- **Solution:** Explicit `IsValid()` method -- **Impact:** Clearer code, safer -- **Files:** `UIHolderObjectBase.cs`, `UIModule.Open.cs` - -### 11. State Check Improvements -- **Problem:** Redundant state checks -- **Solution:** Early returns with validation -- **Impact:** Prevent duplicate calls -- **Files:** `UIBase.cs` - ---- - -## Files Modified Summary - -### New Files Created (1) -- `UIStateMachine.cs` - State transition validation - -### Files Modified (11) -1. `UIMetaRegistry.cs` - Pre-registration -2. `UIResRegistry.cs` - Pre-registration -3. `UIModule.Open.cs` - Index tracking, visibility/depth optimization -4. `UIBase.cs` - Async fixes, state validation -5. `UIBase.Widget.cs` - Widget enumeration optimization -6. `UIMetadataFactory.cs` - String allocation fix -7. `UIMetadataObject.cs` - RuntimeTypeHandle support -8. `UIModule.Cache.cs` - Timer management fixes -9. `UIMetadata.cs` - State validation -10. `UIHolderObjectBase.cs` - Remove implicit operator -11. `UIModule.Initlize.cs` - (if needed for initialization) - -### Documentation Created (3) -- `PHASE1_OPTIMIZATIONS.md` -- `PHASE2_OPTIMIZATIONS.md` -- `PHASE3_OPTIMIZATIONS.md` - ---- - -## Optimization Categories - -### Memory Optimizations -- ✅ Eliminate reflection allocations (Phase 1) -- ✅ Eliminate async state machine allocations (Phase 1) -- ✅ Eliminate widget enumeration allocations (Phase 2) -- ✅ Eliminate string allocations (Phase 2) - -### Performance Optimizations -- ✅ O(n) → O(1) window lookup (Phase 1) -- ✅ Early exit in visibility sorting (Phase 2) -- ✅ Partial depth updates (Phase 3) -- ✅ Avoid unnecessary Canvas updates (Phase 3) - -### Code Quality Improvements -- ✅ State machine validation (Phase 3) -- ✅ Fixed typos (Phase 2) -- ✅ Better error handling (Phase 2, 3) -- ✅ Clearer intent (Phase 3) - ---- - -## Testing Strategy - -### Unit Tests -```csharp -// State machine validation -TestInvalidStateTransition() -TestValidStateTransitions() - -// Performance tests -TestUIOpenPerformance() -TestWindowSwitchPerformance() -TestWidgetUpdateAllocations() - -// Memory tests -TestUIOpenAllocations() -TestWidgetCreationAllocations() - -// Functionality tests -TestDepthSortingOptimization() -TestHolderIsValid() -TestDuplicateOpenPrevention() -``` - -### Integration Tests -- Open/close multiple windows -- Switch between windows rapidly -- Create/destroy many widgets -- Test cache expiration -- Test state transitions - -### Performance Profiling -- Unity Profiler memory tracking -- GC.Alloc monitoring -- Frame time measurements -- Startup time measurement - ---- - -## Backward Compatibility - -✅ **100% Backward Compatible** -- No breaking changes to public API -- Existing UI code requires no modifications -- All optimizations are transparent to users -- Fallback to runtime reflection if pre-registration fails - ---- - -## Known Limitations - -1. **Startup Time:** +10-30ms one-time cost for pre-registration -2. **Memory Overhead:** ~32 bytes per layer for IndexMap -3. **State Machine:** Small overhead for validation (negligible) -4. **Assembly Scanning:** May fail on some platforms (graceful fallback) - ---- - -## Recommendations for Usage - -### Best Practices -1. Let pre-registration run at startup (automatic) -2. Use async UI loading for smooth experience -3. Cache frequently used UI windows -4. Use widgets for reusable components -5. Monitor state transitions in debug builds - -### Performance Tips -1. Minimize fullscreen windows (blocks lower windows) -2. Use `[UIUpdate]` attribute only when needed -3. Pool frequently created/destroyed UI -4. Preload critical UI during loading screens -5. Use appropriate cache times - ---- - -## Future Enhancement Opportunities - -### Phase 4 (Architectural) -1. **UI Preloading System** - - Preload critical UI during loading - - Batch loading for better performance - - Memory budget management - -2. **UI Pooling** - - Pool frequently used windows (toasts, tooltips) - - Reduce allocation for transient UI - - Configurable pool sizes - -3. **Performance Metrics** - - Track UI open/close times - - Memory usage per UI - - Frame time impact - - Analytics integration - -4. **Advanced Optimizations** - - Separate update loop for visible windows only - - Batch Canvas updates - - Lazy initialization for widgets - - Virtual scrolling for lists - ---- - -## Conclusion - -The UI module optimizations have achieved: - -✅ **30-50% faster UI operations** -✅ **50-70% less GC pressure** -✅ **Better scalability** with many windows -✅ **Improved debugging** with state validation -✅ **Higher code quality** with fixes and clarity -✅ **100% backward compatible** - -The optimizations are production-ready and will significantly improve user experience, especially on lower-end devices and when many UI windows are active. - ---- - -## Acknowledgments - -All optimizations maintain the original architecture's elegance while adding performance and reliability improvements. The module is now more robust, faster, and easier to debug. diff --git a/Runtime/UI/OPTIMIZATION_SUMMARY.md.meta b/Runtime/UI/OPTIMIZATION_SUMMARY.md.meta deleted file mode 100644 index be8b9be..0000000 --- a/Runtime/UI/OPTIMIZATION_SUMMARY.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: dcb06edbd88c426459ffbcce1ffc484f -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UI/PHASE1_OPTIMIZATIONS.md b/Runtime/UI/PHASE1_OPTIMIZATIONS.md deleted file mode 100644 index de08f90..0000000 --- a/Runtime/UI/PHASE1_OPTIMIZATIONS.md +++ /dev/null @@ -1,236 +0,0 @@ -# Phase 1 UI Module Optimizations - Implementation Summary - -## Date: 2025-12-24 - -## Overview -Successfully implemented Phase 1 critical optimizations for the UI module, targeting reflection overhead, lookup performance, and memory allocations. - ---- - -## ✅ Optimization 1: Pre-Register All UI Types at Startup - -### Problem -- Reflection overhead of 5-10ms on first UI open -- `CustomAttributeData.GetCustomAttributes()` called at runtime -- `GetGenericArguments()` called for every unregistered UI type - -### Solution -**Files Modified:** -- `UIMetaRegistry.cs` -- `UIResRegistry.cs` - -**Changes:** -1. Added `PreRegisterAllUITypes()` method with `[RuntimeInitializeOnLoadMethod]` attribute -2. Scans all assemblies at startup (skips system assemblies) -3. Pre-caches all UIBase-derived types and their metadata -4. Added `PreRegisterAllUIResources()` for UI holder resource paths -5. Split reflection logic into `TryReflectAndRegisterInternal()` for reuse - -**Key Features:** -- Runs automatically at `SubsystemRegistration` phase -- Logs registration time and count -- Graceful error handling for assembly load failures -- One-time execution with `_isPreRegistered` flag - -**Expected Impact:** -- ✅ Eliminate 5-10ms reflection overhead on first UI open -- ✅ All UI types cached at startup -- ✅ Predictable startup time (one-time cost) - ---- - -## ✅ Optimization 2: Replace List.IndexOf with Index Tracking - -### Problem -- `List.IndexOf(meta)` is O(n) linear search -- Called in `MoveToTop()` every time a window is re-opened -- Performance degrades with more windows in a layer - -### Solution -**File Modified:** -- `UIModule.Open.cs` - -**Changes:** -1. Added `IndexMap` dictionary to `LayerData` struct -2. Updated `Push()` to track index when adding windows -3. Updated `Pop()` to maintain indices after removal -4. Replaced `IndexOf()` with O(1) dictionary lookup in `MoveToTop()` -5. Update indices for shifted elements after removal/move - -**Code Changes:** -```csharp -// Before -int currentIdx = layer.OrderList.IndexOf(meta); // O(n) - -// After -if (!layer.IndexMap.TryGetValue(meta.MetaInfo.RuntimeTypeHandle, out int currentIdx)) // O(1) - return; -``` - -**Expected Impact:** -- ✅ O(n) → O(1) lookup performance -- ✅ ~0.5-2ms saved per window switch -- ✅ Scales better with more windows - ---- - -## ✅ Optimization 3: Fix Async State Machine Allocations - -### Problem -- Virtual async methods allocate state machines even when not overridden -- `await UniTask.CompletedTask` allocates 48-64 bytes per call -- Called 3 times per UI open (Initialize, Open, Close) -- Unnecessary GC pressure - -### Solution -**File Modified:** -- `UIBase.cs` - -**Changes:** -1. Removed `async` keyword from virtual methods -2. Changed return pattern from `await UniTask.CompletedTask` to `return UniTask.CompletedTask` -3. Call synchronous methods first, then return completed task - -**Code Changes:** -```csharp -// Before -protected virtual async UniTask OnInitializeAsync() -{ - await UniTask.CompletedTask; // Allocates state machine - OnInitialize(); -} - -// After -protected virtual UniTask OnInitializeAsync() -{ - OnInitialize(); - return UniTask.CompletedTask; // No allocation -} -``` - -**Expected Impact:** -- ✅ Eliminate ~100-200 bytes allocation per UI open -- ✅ Reduce GC pressure -- ✅ Faster execution (no state machine overhead) - ---- - -## Performance Improvements Summary - -| Metric | Before | After | Improvement | -|--------|--------|-------|-------------| -| First UI Open | 5-10ms reflection | 0ms (pre-cached) | **5-10ms faster** | -| Window Switch | O(n) IndexOf | O(1) lookup | **0.5-2ms faster** | -| GC per UI Open | ~150 bytes | ~0 bytes | **150 bytes saved** | -| Startup Time | 0ms | +10-30ms (one-time) | Acceptable trade-off | - ---- - -## Testing Recommendations - -### 1. Startup Performance Test -```csharp -[Test] -public void TestUIPreRegistration() -{ - // Verify all UI types are registered at startup - var registeredCount = UIMetaRegistry.GetRegisteredCount(); - Assert.Greater(registeredCount, 0); - - // Verify no reflection warnings in logs - LogAssert.NoUnexpectedReceived(); -} -``` - -### 2. Window Switch Performance Test -```csharp -[Test] -public async Task TestWindowSwitchPerformance() -{ - await GameApp.UI.ShowUI(); - await GameApp.UI.ShowUI(); - - var stopwatch = Stopwatch.StartNew(); - await GameApp.UI.ShowUI(); // Re-open (triggers MoveToTop) - stopwatch.Stop(); - - Assert.Less(stopwatch.ElapsedMilliseconds, 5); // Should be < 5ms -} -``` - -### 3. Memory Allocation Test -```csharp -[Test] -public async Task TestUIOpenAllocations() -{ - GC.Collect(); - var beforeMemory = GC.GetTotalMemory(true); - - await GameApp.UI.ShowUI(); - - var afterMemory = GC.GetTotalMemory(false); - var allocated = afterMemory - beforeMemory; - - // Should allocate less than before (no async state machines) - Assert.Less(allocated, 1000); // Adjust threshold as needed -} -``` - -### 4. Index Map Consistency Test -```csharp -[Test] -public async Task TestIndexMapConsistency() -{ - // Open multiple windows - await GameApp.UI.ShowUI(); - await GameApp.UI.ShowUI(); - await GameApp.UI.ShowUI(); - - // Close middle window - GameApp.UI.CloseUI(); - - // Verify indices are correct - // (Internal test - would need access to UIModule internals) -} -``` - ---- - -## Verification Checklist - -- [x] Code compiles without errors -- [x] No breaking changes to public API -- [x] Backward compatible with existing UI code -- [ ] Run Unity Editor and verify no errors in console -- [ ] Test UI opening/closing in play mode -- [ ] Profile memory allocations with Unity Profiler -- [ ] Measure startup time increase (should be < 50ms) -- [ ] Test with multiple UI windows open simultaneously -- [ ] Verify UI switching performance improvement - ---- - -## Known Limitations - -1. **Startup Time Increase**: Pre-registration adds 10-30ms to startup (acceptable trade-off) -2. **Memory Overhead**: IndexMap adds ~32 bytes per layer (negligible) -3. **Assembly Scanning**: May fail on some platforms (graceful fallback to runtime reflection) - ---- - -## Next Steps (Phase 2) - -1. Optimize widget enumeration allocations -2. Fix UIMetadataFactory string allocations -3. Optimize SortWindowVisible with early exit -4. Add state machine validation -5. Implement UI preloading system - ---- - -## Notes - -- All changes maintain backward compatibility -- Existing UI code requires no modifications -- Optimizations are transparent to users -- Fallback to runtime reflection if pre-registration fails diff --git a/Runtime/UI/PHASE1_OPTIMIZATIONS.md.meta b/Runtime/UI/PHASE1_OPTIMIZATIONS.md.meta deleted file mode 100644 index dc8369c..0000000 --- a/Runtime/UI/PHASE1_OPTIMIZATIONS.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: e65d5c1ad620ab945a8e79be86b53728 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UI/PHASE2_OPTIMIZATIONS.md b/Runtime/UI/PHASE2_OPTIMIZATIONS.md deleted file mode 100644 index 0d8205f..0000000 --- a/Runtime/UI/PHASE2_OPTIMIZATIONS.md +++ /dev/null @@ -1,434 +0,0 @@ -# Phase 2 UI Module Optimizations - Implementation Summary - -## Date: 2025-12-24 - -## Overview -Successfully implemented Phase 2 high-priority optimizations for the UI module, targeting widget enumeration, string allocations, visibility sorting, and cache management. - ---- - -## ✅ Optimization 1: Widget Dictionary Enumeration Allocations - -### Problem -- `_children.Values` enumeration allocates 40-56 bytes per call -- Called every frame for windows with widgets -- `UpdateChildren()` and `ChildVisible()` both enumerate dictionary - -### Solution -**File Modified:** -- `UIBase.Widget.cs` - -**Changes:** -1. Added `_updateableChildren` list to cache widgets that need updates -2. Changed `UpdateChildren()` to iterate cached list instead of dictionary -3. Changed `ChildVisible()` to use struct enumerator (`foreach (var kvp in _children)`) -4. Updated `AddWidget()` to populate updateable list -5. Updated `RemoveWidget()` to maintain updateable list -6. Updated `DestroyAllChildren()` to clear both collections - -**Code Changes:** -```csharp -// Before -private void UpdateChildren() -{ - var values = _children.Values; // Allocates enumerator - foreach (var meta in values) - { - if (meta.View.State == UIState.Opened && meta.MetaInfo.NeedUpdate) - { - meta.View.InternalUpdate(); - } - } -} - -// After -private readonly List _updateableChildren = new(); - -private void UpdateChildren() -{ - // Use cached list - no allocation - for (int i = 0; i < _updateableChildren.Count; i++) - { - var meta = _updateableChildren[i]; - if (meta.View.State == UIState.Opened) - { - meta.View.InternalUpdate(); - } - } -} -``` - -**Expected Impact:** -- ✅ Eliminate 40-56 bytes allocation per frame per window with widgets -- ✅ Faster iteration (direct list access vs dictionary enumeration) -- ✅ Only iterate widgets that actually need updates - ---- - -## ✅ Optimization 2: UIMetadataFactory String Allocations - -### Problem -- `type.FullName` allocates new string on every widget creation -- String used as dictionary key in object pool -- Unnecessary allocation for frequently created widgets - -### Solution -**Files Modified:** -- `UIMetadataFactory.cs` -- `UIMetadataObject.cs` - -**Changes:** -1. Replaced string-based pooling with `RuntimeTypeHandle`-based caching -2. Added `WidgetMetadataCache` dictionary using `RuntimeTypeHandle` as key -3. Changed `GetWidgetMetadata()` to accept `RuntimeTypeHandle` directly -4. Added overload to `UIMetadataObject.Create()` accepting `RuntimeTypeHandle` -5. Simplified `ReturnToPool()` (widgets are now cached, not pooled) - -**Code Changes:** -```csharp -// Before -private static UIMetadata GetFromPool(Type type) -{ - string typeHandleKey = type.FullName; // Allocates string - UIMetadataObject metadataObj = m_UIMetadataPool.Spawn(typeHandleKey); - // ... -} - -// After -private static readonly Dictionary WidgetMetadataCache = new(); - -private static UIMetadata GetFromPool(RuntimeTypeHandle handle) -{ - // Use RuntimeTypeHandle directly - no string allocation - if (WidgetMetadataCache.TryGetValue(handle, out var metadataObj)) - { - if (metadataObj != null && metadataObj.Target != null) - { - return (UIMetadata)metadataObj.Target; - } - } - // ... -} -``` - -**Expected Impact:** -- ✅ Eliminate 40+ bytes string allocation per widget creation -- ✅ Faster lookup (RuntimeTypeHandle comparison vs string comparison) -- ✅ Better memory locality - ---- - -## ✅ Optimization 3: SortWindowVisible Early Exit - -### Problem -- Original logic iterates all windows even after finding fullscreen -- Unclear logic with boolean flag -- No early exit optimization - -### Solution -**File Modified:** -- `UIModule.Open.cs` - -**Changes:** -1. Split logic into two phases: find fullscreen, then set visibility -2. Added early exit when fullscreen window found -3. Clearer logic with explicit index tracking -4. Separate fast paths for "no fullscreen" and "has fullscreen" cases - -**Code Changes:** -```csharp -// Before -private void SortWindowVisible(int layer) -{ - var list = _openUI[layer].OrderList; - bool shouldHide = false; - - for (int i = list.Count - 1; i >= 0; i--) - { - var meta = list[i]; - meta.View.Visible = !shouldHide; - shouldHide |= meta.MetaInfo.FullScreen && meta.State == UIState.Opened; - } -} - -// After -private void SortWindowVisible(int layer) -{ - var list = _openUI[layer].OrderList; - int count = list.Count; - - // Find topmost fullscreen window (early exit) - int fullscreenIdx = -1; - for (int i = count - 1; i >= 0; i--) - { - var meta = list[i]; - if (meta.MetaInfo.FullScreen && meta.State == UIState.Opened) - { - fullscreenIdx = i; - break; // Early exit - } - } - - // Set visibility based on fullscreen index - if (fullscreenIdx == -1) - { - // Fast path: no fullscreen, all visible - for (int i = 0; i < count; i++) - { - list[i].View.Visible = true; - } - } - else - { - // Hide below fullscreen, show from fullscreen onwards - for (int i = 0; i < count; i++) - { - list[i].View.Visible = (i >= fullscreenIdx); - } - } -} -``` - -**Expected Impact:** -- ✅ Early exit when fullscreen found (saves iterations) -- ✅ Clearer, more maintainable code -- ✅ Separate fast paths for common cases - ---- - -## ✅ Optimization 4: Cache Timer Management - -### Problem -- Typo: `tiemrId` instead of `timerId` -- Typo: `OnTimerDiposeWindow` instead of `OnTimerDisposeWindow` -- No validation if timer creation fails -- Poor error messages -- No null checks on timer callback args - -### Solution -**File Modified:** -- `UIModule.Cache.cs` - -**Changes:** -1. Fixed typo: `tiemrId` → `timerId` -2. Fixed typo: `OnTimerDiposeWindow` → `OnTimerDisposeWindow` -3. Added validation for timer creation failure -4. Improved error messages with context -5. Added null checks in timer callback -6. Used named parameters for clarity -7. Better null-conditional operator usage - -**Code Changes:** -```csharp -// Before -int tiemrId = -1; -tiemrId = _timerModule.AddTimer(OnTimerDiposeWindow, uiMetadata.MetaInfo.CacheTime, false, true, uiMetadata); - -private void OnTimerDiposeWindow(object[] args) -{ - UIMetadata meta = args[0] as UIMetadata; - meta?.Dispose(); - RemoveFromCache(meta.MetaInfo.RuntimeTypeHandle); -} - -// After -int timerId = -1; -timerId = _timerModule.AddTimer( - OnTimerDisposeWindow, - uiMetadata.MetaInfo.CacheTime, - oneShot: true, - ignoreTimeScale: true, - uiMetadata -); - -if (timerId <= 0) -{ - Log.Warning($"Failed to create cache timer for {uiMetadata.UILogicType.Name}"); -} - -private void OnTimerDisposeWindow(object[] args) -{ - if (args == null || args.Length == 0) return; - - UIMetadata meta = args[0] as UIMetadata; - if (meta != null) - { - meta.Dispose(); - RemoveFromCache(meta.MetaInfo.RuntimeTypeHandle); - } -} -``` - -**Expected Impact:** -- ✅ Fixed typos (better code quality) -- ✅ Better error detection and logging -- ✅ Prevent null reference exceptions -- ✅ More maintainable code - ---- - -## Performance Improvements Summary - -| Metric | Before | After | Improvement | -|--------|--------|-------|-------------| -| Widget Update (per frame) | 56 bytes | 0 bytes | **56 bytes saved** | -| Widget Creation | 40 bytes | 0 bytes | **40 bytes saved** | -| Visibility Sort | No early exit | Early exit | **Faster** | -| Code Quality | Typos present | Fixed | **Better** | - -**Total Per-Frame Savings (per window with widgets):** -- **~100 bytes GC allocation eliminated** -- **Faster widget updates** (list iteration vs dictionary enumeration) -- **Clearer, more maintainable code** - ---- - -## Testing Recommendations - -### 1. Widget Update Performance Test -```csharp -[Test] -public void TestWidgetUpdateAllocations() -{ - var window = await GameApp.UI.ShowUI(); - - GC.Collect(); - var beforeMemory = GC.GetTotalMemory(true); - - // Simulate multiple frames - for (int i = 0; i < 100; i++) - { - window.InternalUpdate(); - } - - var afterMemory = GC.GetTotalMemory(false); - var allocated = afterMemory - beforeMemory; - - // Should allocate much less than before - Assert.Less(allocated, 1000); // Adjust threshold -} -``` - -### 2. Widget Creation Allocation Test -```csharp -[Test] -public async Task TestWidgetCreationAllocations() -{ - var window = await GameApp.UI.ShowUI(); - - GC.Collect(); - var beforeMemory = GC.GetTotalMemory(true); - - // Create multiple widgets - for (int i = 0; i < 10; i++) - { - await window.CreateWidgetAsync(parent); - } - - var afterMemory = GC.GetTotalMemory(false); - var allocated = afterMemory - beforeMemory; - - // Should not allocate strings for type names - Assert.Less(allocated, 5000); // Adjust based on widget size -} -``` - -### 3. Visibility Sort Performance Test -```csharp -[Test] -public async Task TestVisibilitySortPerformance() -{ - // Open multiple windows - await GameApp.UI.ShowUI(); - await GameApp.UI.ShowUI(); - await GameApp.UI.ShowUI(); - await GameApp.UI.ShowUI(); - - var stopwatch = Stopwatch.StartNew(); - - // Trigger visibility sort - await GameApp.UI.ShowUI(); // Re-open - - stopwatch.Stop(); - - // Should be fast with early exit - Assert.Less(stopwatch.ElapsedMilliseconds, 2); -} -``` - -### 4. Cache Timer Test -```csharp -[Test] -public async Task TestCacheTimerManagement() -{ - // Open window with cache time - await GameApp.UI.ShowUI(); - GameApp.UI.CloseUI(); - - // Verify window is cached - // Wait for cache expiration - await UniTask.Delay(TimeSpan.FromSeconds(cacheTime + 1)); - - // Verify window was disposed - LogAssert.NoUnexpectedReceived(); -} -``` - ---- - -## Verification Checklist - -- [x] Code compiles without errors -- [x] No breaking changes to public API -- [x] Backward compatible with existing UI code -- [x] Fixed all typos -- [ ] Run Unity Editor and verify no errors -- [ ] Test widget creation and updates -- [ ] Profile memory allocations -- [ ] Test cache timer expiration -- [ ] Verify visibility sorting with fullscreen windows - ---- - -## Code Quality Improvements - -1. **Fixed Typos:** - - `tiemrId` → `timerId` - - `OnTimerDiposeWindow` → `OnTimerDisposeWindow` - -2. **Better Error Handling:** - - Null checks in timer callbacks - - Validation of timer creation - - Improved error messages - -3. **Clearer Logic:** - - Explicit early exit in visibility sorting - - Named parameters for timer creation - - Better comments - ---- - -## Known Limitations - -1. **Widget Update List:** Requires manual maintenance (add/remove) -2. **Cache Dictionary:** Uses RuntimeTypeHandle which may have hash collisions (rare) -3. **Visibility Sort:** Two-pass algorithm (could be optimized further) - ---- - -## Next Steps (Phase 3) - -1. Add state machine validation -2. Optimize depth sorting (only update changed windows) -3. Implement UI preloading system -4. Add UI pooling for frequent windows -5. Implement performance metrics - ---- - -## Notes - -- All optimizations maintain backward compatibility -- No changes required to existing UI code -- Significant reduction in per-frame allocations -- Better code quality and maintainability diff --git a/Runtime/UI/PHASE2_OPTIMIZATIONS.md.meta b/Runtime/UI/PHASE2_OPTIMIZATIONS.md.meta deleted file mode 100644 index 11c336c..0000000 --- a/Runtime/UI/PHASE2_OPTIMIZATIONS.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 8eecca0a49896024eb62845a03e74f11 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UI/PHASE3_OPTIMIZATIONS.md b/Runtime/UI/PHASE3_OPTIMIZATIONS.md deleted file mode 100644 index 8f599b1..0000000 --- a/Runtime/UI/PHASE3_OPTIMIZATIONS.md +++ /dev/null @@ -1,431 +0,0 @@ -# Phase 3 UI Module Optimizations - Implementation Summary - -## Date: 2025-12-24 - -## Overview -Successfully implemented Phase 3 medium-priority optimizations for the UI module, focusing on state validation, depth sorting optimization, and code quality improvements. - ---- - -## ✅ Optimization 1: UI State Machine Validation - -### Problem -- No validation of state transitions -- Invalid state transitions could cause bugs -- Difficult to debug lifecycle issues -- No clear documentation of valid state flows - -### Solution -**Files Created:** -- `UIStateMachine.cs` (new file) - -**Files Modified:** -- `UIBase.cs` -- `UIMetadata.cs` - -**Changes:** -1. Created `UIStateMachine` class with state transition validation -2. Defined valid state transitions in dictionary -3. Added `ValidateTransition()` method with logging -4. Added `GetStateDescription()` for debugging -5. Integrated validation into all lifecycle methods: - - `CreateUI()` → CreatedUI - - `InternalInitlized()` → Initialized - - `InternalOpen()` → Opened - - `InternalClose()` → Closed - - `InternalDestroy()` → Destroying → Destroyed - -**Valid State Transitions:** -``` -Uninitialized → CreatedUI -CreatedUI → Loaded -Loaded → Initialized -Initialized → Opened -Opened → Closed | Destroying -Closed → Opened | Destroying -Destroying → Destroyed -Destroyed → (none) -``` - -**Code Example:** -```csharp -internal async UniTask InternalOpen() -{ - if (_state == UIState.Opened) - return; // Already open - - if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Opened)) - return; // Invalid transition, logged - - _state = UIState.Opened; - // ... rest of logic -} -``` - -**Expected Impact:** -- ✅ Catch invalid state transitions early -- ✅ Better error messages with UI name -- ✅ Prevent crashes from invalid lifecycle calls -- ✅ Easier debugging of UI lifecycle issues - ---- - -## ✅ Optimization 2: Depth Sorting Optimization - -### Problem -- `SortWindowDepth()` recalculates depth for ALL windows in layer -- Called even when only one window changed -- Unnecessary Canvas.sortingOrder updates -- Performance degrades with more windows - -### Solution -**File Modified:** -- `UIModule.Open.cs` - -**Changes:** -1. Added `startIndex` parameter to `SortWindowDepth()` -2. Only update windows from `startIndex` onwards -3. Check if depth changed before setting (avoid Canvas update) -4. Integrated with `MoveToTop()` to only update affected windows - -**Code Changes:** -```csharp -// Before -private void SortWindowDepth(int layer) -{ - var list = _openUI[layer].OrderList; - int baseDepth = layer * LAYER_DEEP; - - for (int i = 0; i < list.Count; i++) - { - list[i].View.Depth = baseDepth + i * WINDOW_DEEP; // Always set - } -} - -// After -private void SortWindowDepth(int layer, int startIndex = 0) -{ - var list = _openUI[layer].OrderList; - int baseDepth = layer * LAYER_DEEP; - - // Only update from startIndex onwards - for (int i = startIndex; i < list.Count; i++) - { - int newDepth = baseDepth + i * WINDOW_DEEP; - - // Only set if changed - if (list[i].View.Depth != newDepth) - { - list[i].View.Depth = newDepth; - } - } -} - -// MoveToTop now only updates affected windows -private void MoveToTop(UIMetadata meta) -{ - // ... move logic ... - - // Only update depth for affected windows - SortWindowDepth(meta.MetaInfo.UILayer, currentIdx); -} -``` - -**Expected Impact:** -- ✅ Reduce unnecessary Canvas.sortingOrder updates -- ✅ Faster window switching (only update changed windows) -- ✅ Better performance with many windows in a layer - ---- - -## ✅ Optimization 3: Remove Implicit Bool Operator - -### Problem -- `UIHolderObjectBase` had confusing implicit bool operator -- Overloaded Unity's null check behavior -- Could cause unexpected behavior -- Not clear intent when used - -### Solution -**File Modified:** -- `UIHolderObjectBase.cs` - -**Changes:** -1. Removed `implicit operator bool()` -2. Added explicit `IsValid()` method -3. Updated usage sites to use `IsValid()` -4. Clearer intent and safer code - -**Code Changes:** -```csharp -// Before -public static implicit operator bool(UIHolderObjectBase exists) -{ - if (exists == null) return false; - return exists.IsAlive; -} - -// Usage -if (meta.View?.Holder) // Confusing -{ - // ... -} - -// After -public bool IsValid() -{ - return this != null && _isAlive; -} - -// Usage -if (meta.View?.Holder != null && meta.View.Holder.IsValid()) // Clear -{ - // ... -} -``` - -**Expected Impact:** -- ✅ Clearer intent in code -- ✅ Avoid operator confusion -- ✅ Safer null checking -- ✅ Better maintainability - ---- - -## ✅ Optimization 4: State Check Improvements - -### Problem -- Redundant state checks in lifecycle methods -- No early returns for already-open windows -- Could call lifecycle methods multiple times - -### Solution -**File Modified:** -- `UIBase.cs` - -**Changes:** -1. Added early return in `InternalOpen()` if already open -2. Added early return in `InternalClose()` if not open -3. Combined with state machine validation -4. Better error prevention - -**Code Changes:** -```csharp -// Before -internal async UniTask InternalOpen() -{ - if (_state != UIState.Opened) - { - _state = UIState.Opened; - // ... logic - } -} - -// After -internal async UniTask InternalOpen() -{ - if (_state == UIState.Opened) - return; // Already open, early exit - - if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Opened)) - return; // Invalid transition - - _state = UIState.Opened; - // ... logic -} -``` - -**Expected Impact:** -- ✅ Prevent duplicate lifecycle calls -- ✅ Better error detection -- ✅ Clearer logic flow - ---- - -## Performance Improvements Summary - -| Metric | Before | After | Improvement | -|--------|--------|-------|-------------| -| **Depth Updates** | All windows | Only changed | **Faster** | -| **Canvas Updates** | Always set | Only if changed | **Fewer updates** | -| **State Validation** | None | Full validation | **Better debugging** | -| **Code Clarity** | Implicit operator | Explicit method | **Clearer** | - ---- - -## Testing Recommendations - -### 1. State Machine Validation Test -```csharp -[Test] -public async Task TestInvalidStateTransition() -{ - var window = await GameApp.UI.ShowUI(); - - // Try to open already-open window - await window.InternalOpen(); // Should log error and return - - // Verify no crash - Assert.AreEqual(UIState.Opened, window.State); -} - -[Test] -public async Task TestValidStateTransitions() -{ - var window = new TestWindow(); - - // Uninitialized → CreatedUI - window.CreateUI(); - Assert.AreEqual(UIState.CreatedUI, window.State); - - // CreatedUI → Loaded → Initialized → Opened - await window.InternalInitlized(); - await window.InternalOpen(); - Assert.AreEqual(UIState.Opened, window.State); - - // Opened → Closed - await window.InternalClose(); - Assert.AreEqual(UIState.Closed, window.State); - - // Closed → Destroying → Destroyed - await window.InternalDestroy(); - Assert.AreEqual(UIState.Destroyed, window.State); -} -``` - -### 2. Depth Sorting Optimization Test -```csharp -[Test] -public async Task TestDepthSortingOptimization() -{ - // Open multiple windows - var w1 = await GameApp.UI.ShowUI(); - var w2 = await GameApp.UI.ShowUI(); - var w3 = await GameApp.UI.ShowUI(); - - int initialDepth1 = w1.Depth; - int initialDepth2 = w2.Depth; - int initialDepth3 = w3.Depth; - - // Re-open w1 (should move to top) - await GameApp.UI.ShowUI(); - - // w1 should have new depth, w2 and w3 should be updated - Assert.AreNotEqual(initialDepth1, w1.Depth); -} -``` - -### 3. IsValid() Method Test -```csharp -[Test] -public void TestHolderIsValid() -{ - var holder = CreateTestHolder(); - - Assert.IsTrue(holder.IsValid()); - - Destroy(holder.gameObject); - - // After destroy, should be invalid - Assert.IsFalse(holder.IsValid()); -} -``` - -### 4. State Check Test -```csharp -[Test] -public async Task TestDuplicateOpenPrevention() -{ - var window = await GameApp.UI.ShowUI(); - - int openCallCount = 0; - window.OnWindowBeforeShowEvent += () => openCallCount++; - - // Try to open again - await window.InternalOpen(); - - // Should only be called once - Assert.AreEqual(1, openCallCount); -} -``` - ---- - -## Verification Checklist - -- [x] Code compiles without errors -- [x] No breaking changes to public API -- [x] State machine validates all transitions -- [x] Depth sorting only updates changed windows -- [x] Implicit operator removed -- [ ] Run Unity Editor and verify no errors -- [ ] Test invalid state transitions -- [ ] Test depth sorting with multiple windows -- [ ] Verify IsValid() works correctly -- [ ] Test duplicate lifecycle calls - ---- - -## Code Quality Improvements - -1. **State Machine:** - - Clear documentation of valid transitions - - Better error messages with UI name - - Easier debugging - -2. **Depth Sorting:** - - Partial updates instead of full recalculation - - Avoid unnecessary Canvas updates - - Better performance - -3. **Implicit Operator:** - - Removed confusing operator overload - - Explicit `IsValid()` method - - Clearer intent - -4. **State Checks:** - - Early returns for invalid states - - Combined with validation - - Better error prevention - ---- - -## Known Limitations - -1. **State Machine:** Adds small overhead to lifecycle calls (negligible) -2. **Depth Sorting:** Still O(n) but with smaller n -3. **IsValid():** Requires explicit call instead of implicit check - ---- - -## Next Steps (Future Enhancements) - -1. Implement UI preloading system -2. Add UI pooling for frequent windows -3. Implement performance metrics/profiling -4. Add UI analytics tracking -5. Optimize visibility sorting further - ---- - -## Combined Results (Phase 1 + 2 + 3) - -| Metric | Original | Optimized | Total Improvement | -|--------|----------|-----------|-------------------| -| **First UI Open** | 5-10ms | 0ms | **5-10ms faster** | -| **Window Switch** | O(n) | O(1) | **0.5-2ms faster** | -| **Per UI Open** | ~150 bytes | 0 bytes | **150 bytes saved** | -| **Per Frame (widgets)** | ~100 bytes | 0 bytes | **100 bytes saved** | -| **Widget Creation** | 40 bytes | 0 bytes | **40 bytes saved** | -| **Depth Updates** | All windows | Only changed | **Fewer Canvas updates** | -| **State Validation** | None | Full | **Better debugging** | - ---- - -## Notes - -- All optimizations maintain backward compatibility -- State machine adds safety without performance cost -- Depth sorting optimization reduces Canvas updates -- Code is clearer and more maintainable -- Better error detection and debugging capabilities diff --git a/Runtime/UI/PHASE3_OPTIMIZATIONS.md.meta b/Runtime/UI/PHASE3_OPTIMIZATIONS.md.meta deleted file mode 100644 index 4014c4c..0000000 --- a/Runtime/UI/PHASE3_OPTIMIZATIONS.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 3cc945d19bf80634eb057f3a5c8653ca -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UI/UIBase/UIBase.Widget.cs b/Runtime/UI/UIBase/UIBase.Widget.cs index 629a9c8..e6abe1e 100644 --- a/Runtime/UI/UIBase/UIBase.Widget.cs +++ b/Runtime/UI/UIBase/UIBase.Widget.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Threading; using AlicizaX; using Cysharp.Threading.Tasks; using UnityEngine; @@ -70,7 +71,7 @@ namespace AlicizaX.UI.Runtime { metadata.CreateUI(); await UIHolderFactory.CreateUIResourceAsync(metadata, parent, this); - await ProcessWidget(metadata, visible); + await ProcessWidget(metadata, visible, metadata.CancellationToken); return (UIBase)metadata.View; } @@ -105,7 +106,7 @@ namespace AlicizaX.UI.Runtime metadata.CreateUI(); UIBase widget = (UIBase)metadata.View; widget.BindUIHolder(holder, this); - await ProcessWidget(metadata, true); + await ProcessWidget(metadata, true, metadata.CancellationToken); return (T)widget; } @@ -142,14 +143,14 @@ namespace AlicizaX.UI.Runtime #endregion - private async UniTask ProcessWidget(UIMetadata meta, bool visible) + private async UniTask ProcessWidget(UIMetadata meta, bool visible, CancellationToken cancellationToken = default) { if (!AddWidget(meta)) return; - await meta.View.InternalInitlized(); + await meta.View.InternalInitlized(cancellationToken); meta.View.Visible = visible; if (meta.View.Visible) { - await meta.View.InternalOpen(); + await meta.View.InternalOpen(cancellationToken); } } @@ -176,6 +177,7 @@ namespace AlicizaX.UI.Runtime { if (_children.Remove(widget, out var meta)) { + meta.CancelAsyncOperations(); await widget.InternalClose(); // Remove from updateable list if present diff --git a/Runtime/UI/UIBase/UIBase.cs b/Runtime/UI/UIBase/UIBase.cs index 1773b73..b8b82da 100644 --- a/Runtime/UI/UIBase/UIBase.cs +++ b/Runtime/UI/UIBase/UIBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using AlicizaX; using Cysharp.Threading.Tasks; using UnityEngine; @@ -68,7 +69,7 @@ namespace AlicizaX.UI.Runtime /// /// 如果重写当前方法 则同步OnInitialize不会调用 /// - protected virtual UniTask OnInitializeAsync() + protected virtual UniTask OnInitializeAsync(CancellationToken cancellationToken = default) { OnInitialize(); return UniTask.CompletedTask; @@ -77,7 +78,7 @@ namespace AlicizaX.UI.Runtime /// /// 如果重写当前方法 则同步OnOpen不会调用 /// - protected virtual UniTask OnOpenAsync() + protected virtual UniTask OnOpenAsync(CancellationToken cancellationToken = default) { OnOpen(); return UniTask.CompletedTask; @@ -86,7 +87,7 @@ namespace AlicizaX.UI.Runtime /// /// 如果重写当前方法 则同步OnClose不会调用 /// - protected virtual UniTask OnCloseAsync() + protected virtual UniTask OnCloseAsync(CancellationToken cancellationToken = default) { OnClose(); return UniTask.CompletedTask; @@ -203,18 +204,18 @@ namespace AlicizaX.UI.Runtime internal abstract void BindUIHolder(UIHolderObjectBase holder, UIBase owner); - internal async UniTask InternalInitlized() + internal async UniTask InternalInitlized(CancellationToken cancellationToken = default) { if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Initialized)) return; _state = UIState.Initialized; Holder.OnWindowInitEvent?.Invoke(); - await OnInitializeAsync(); + await OnInitializeAsync(cancellationToken); OnRegisterEvent(EventListenerProxy); } - internal async UniTask InternalOpen() + internal async UniTask InternalOpen(CancellationToken cancellationToken = default) { if (_state == UIState.Opened) return; // Already open @@ -225,11 +226,11 @@ namespace AlicizaX.UI.Runtime _state = UIState.Opened; Visible = true; Holder.OnWindowBeforeShowEvent?.Invoke(); - await OnOpenAsync(); + await OnOpenAsync(cancellationToken); Holder.OnWindowAfterShowEvent?.Invoke(); } - internal async UniTask InternalClose() + internal async UniTask InternalClose(CancellationToken cancellationToken = default) { if (_state != UIState.Opened) return; // Not open, nothing to close @@ -238,7 +239,7 @@ namespace AlicizaX.UI.Runtime return; Holder.OnWindowBeforeClosedEvent?.Invoke(); - await OnCloseAsync(); + await OnCloseAsync(cancellationToken); _state = UIState.Closed; Visible = false; Holder.OnWindowAfterClosedEvent?.Invoke(); diff --git a/Runtime/UI/UIBase/UIStateMachine.cs b/Runtime/UI/UIBase/UIStateMachine.cs index c8a39ad..016963c 100644 --- a/Runtime/UI/UIBase/UIStateMachine.cs +++ b/Runtime/UI/UIBase/UIStateMachine.cs @@ -3,10 +3,7 @@ using System.Collections.Generic; namespace AlicizaX.UI.Runtime { - /// - /// UI State Machine - Validates state transitions for UI lifecycle. - /// Helps catch bugs early and ensures proper UI lifecycle management. - /// + internal static class UIStateMachine { private static readonly Dictionary> _validTransitions = new() @@ -21,24 +18,11 @@ namespace AlicizaX.UI.Runtime [UIState.Destroyed] = new() { } }; - /// - /// Checks if a state transition is valid. - /// - /// Current state - /// Target state - /// True if transition is valid public static bool IsValidTransition(UIState from, UIState to) { return _validTransitions.TryGetValue(from, out var validStates) && validStates.Contains(to); } - /// - /// Validates a state transition and logs error if invalid. - /// - /// Name of the UI for logging - /// Current state - /// Target state - /// True if transition is valid public static bool ValidateTransition(string uiName, UIState from, UIState to) { if (IsValidTransition(from, to)) @@ -48,11 +32,7 @@ namespace AlicizaX.UI.Runtime return false; } - /// - /// Gets all valid next states from the current state. - /// - /// Current state - /// Set of valid next states + public static HashSet GetValidNextStates(UIState currentState) { return _validTransitions.TryGetValue(currentState, out var states) @@ -60,9 +40,6 @@ namespace AlicizaX.UI.Runtime : new HashSet(); } - /// - /// Gets a human-readable description of the state. - /// public static string GetStateDescription(UIState state) { return state switch