[Opt] AppService架构 UI加载&调试 ObjectPool优化 Timer模块优化
This commit is contained in:
parent
8cc5025796
commit
de926116aa
@ -8,38 +8,46 @@ namespace AlicizaX.Timer.Editor
|
|||||||
[CustomEditor(typeof(TimerComponent))]
|
[CustomEditor(typeof(TimerComponent))]
|
||||||
internal sealed class TimerComponentInspector : GameFrameworkInspector
|
internal sealed class TimerComponentInspector : GameFrameworkInspector
|
||||||
{
|
{
|
||||||
private const double UPDATE_INTERVAL = 0.25d;
|
private const double UPDATE_INTERVAL = 0.02d;
|
||||||
private const int MAX_DISPLAY_COUNT = 20;
|
private const int DISPLAY_COUNT = 32;
|
||||||
|
private const int MIN_INITIAL_CAPACITY = 256;
|
||||||
|
private const int MAX_INITIAL_CAPACITY = 16384;
|
||||||
|
private const int CAPACITY_STEP = 256;
|
||||||
|
|
||||||
private TimerDebugInfo[] _timerBuffer;
|
private readonly TimerDebugInfo[] _timerBuffer = new TimerDebugInfo[DISPLAY_COUNT];
|
||||||
#if UNITY_EDITOR
|
private readonly TimerDebugInfo[] _staleBuffer = new TimerDebugInfo[DISPLAY_COUNT];
|
||||||
private TimerDebugInfo[] _leakBuffer;
|
|
||||||
#endif
|
|
||||||
private double _lastUpdateTime;
|
private double _lastUpdateTime;
|
||||||
private int _cachedActiveCount;
|
private SerializedProperty _initialCapacityProperty;
|
||||||
private int _cachedPoolCapacity;
|
|
||||||
private int _cachedPeakActiveCount;
|
|
||||||
private int _cachedFreeCount;
|
|
||||||
private string _cachedUsageText;
|
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
base.OnInspectorGUI();
|
base.OnInspectorGUI();
|
||||||
|
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
|
DrawConfiguration();
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
DrawRuntimeDebugInfo();
|
DrawRuntimeDebugInfo();
|
||||||
|
RequestRuntimeRepaint();
|
||||||
|
}
|
||||||
|
|
||||||
if (EditorApplication.isPlaying)
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_initialCapacityProperty = serializedObject.FindProperty("_initialCapacity");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawConfiguration()
|
||||||
|
{
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
EditorGUILayout.LabelField("Configuration", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
int capacity = _initialCapacityProperty.intValue;
|
||||||
|
int sliderValue = EditorGUILayout.IntSlider("Initial Capacity", capacity, MIN_INITIAL_CAPACITY, MAX_INITIAL_CAPACITY);
|
||||||
|
sliderValue = AlignCapacity(sliderValue);
|
||||||
|
if (sliderValue != capacity)
|
||||||
{
|
{
|
||||||
double currentTime = EditorApplication.timeSinceStartup;
|
_initialCapacityProperty.intValue = sliderValue;
|
||||||
if (currentTime - _lastUpdateTime >= UPDATE_INTERVAL)
|
|
||||||
{
|
|
||||||
_lastUpdateTime = currentTime;
|
|
||||||
Repaint();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox(Utility.Text.Format("Rounded by {0}. Runtime allocates timer pages during Awake/prewarm.", CAPACITY_STEP), MessageType.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRuntimeDebugInfo()
|
private void DrawRuntimeDebugInfo()
|
||||||
@ -56,30 +64,31 @@ namespace AlicizaX.Timer.Editor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(timerService is ITimerDebugService timerDebugService))
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Timer debug service is not available.", MessageType.Info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
timerService.GetStatistics(out _cachedActiveCount, out _cachedPoolCapacity, out _cachedPeakActiveCount, out _cachedFreeCount);
|
timerDebugService.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
|
||||||
_cachedUsageText = _cachedPoolCapacity > 0
|
|
||||||
? Utility.Text.Format("{0:F1}%", (float)_cachedActiveCount / _cachedPoolCapacity * 100f)
|
|
||||||
: "0.0%";
|
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
EditorGUILayout.LabelField("Runtime Debug", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Runtime Debug", EditorStyles.boldLabel);
|
||||||
EditorGUILayout.LabelField("Active Timers", _cachedActiveCount.ToString());
|
DrawStatistic("Active Timers", activeCount);
|
||||||
EditorGUILayout.LabelField("Pool Capacity", _cachedPoolCapacity.ToString());
|
DrawStatistic("Pool Capacity", poolCapacity);
|
||||||
EditorGUILayout.LabelField("Peak Active Count", _cachedPeakActiveCount.ToString());
|
DrawStatistic("Peak Active Count", peakActiveCount);
|
||||||
EditorGUILayout.LabelField("Free Slots", _cachedFreeCount.ToString());
|
DrawStatistic("Free Slots", freeCount);
|
||||||
EditorGUILayout.LabelField("Pool Usage", _cachedUsageText);
|
DrawUsageBar("Active Usage", activeCount, poolCapacity);
|
||||||
|
DrawUsageBar("Peak Usage", peakActiveCount, poolCapacity);
|
||||||
|
|
||||||
DrawTimerList(timerService, _cachedActiveCount);
|
DrawTimerList(timerDebugService, activeCount);
|
||||||
#if UNITY_EDITOR
|
DrawStaleTimerList(timerDebugService, activeCount);
|
||||||
DrawLeakDetection(timerService, _cachedActiveCount);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawTimerList(ITimerService debug, int activeCount)
|
private void DrawTimerList(ITimerDebugService timerDebugService, int activeCount)
|
||||||
{
|
{
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
EditorGUILayout.LabelField("Active Timers", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Active Timer Sample", EditorStyles.boldLabel);
|
||||||
|
|
||||||
if (activeCount <= 0)
|
if (activeCount <= 0)
|
||||||
{
|
{
|
||||||
@ -87,84 +96,93 @@ namespace AlicizaX.Timer.Editor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureTimerBuffer(activeCount);
|
int timerCount = timerDebugService.GetAllTimers(_timerBuffer);
|
||||||
int timerCount = debug.GetAllTimers(_timerBuffer);
|
if (activeCount > DISPLAY_COUNT)
|
||||||
int displayCount = Mathf.Min(timerCount, MAX_DISPLAY_COUNT);
|
|
||||||
|
|
||||||
if (displayCount < activeCount)
|
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox(Utility.Text.Format("Showing first {0} timers of {1}.", displayCount, activeCount), MessageType.Info);
|
EditorGUILayout.HelpBox(Utility.Text.Format("Showing first {0} timers of {1}.", timerCount, activeCount), MessageType.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < displayCount; i++)
|
for (int i = 0; i < timerCount; i++)
|
||||||
{
|
{
|
||||||
TimerDebugInfo timer = _timerBuffer[i];
|
DrawTimerInfo(ref _timerBuffer[i]);
|
||||||
bool isLoop = (timer.Flags & TimerDebugFlags.Loop) != 0;
|
|
||||||
bool isRunning = (timer.Flags & TimerDebugFlags.Running) != 0;
|
|
||||||
bool isUnscaled = (timer.Flags & TimerDebugFlags.Unscaled) != 0;
|
|
||||||
string label = Utility.Text.Format(
|
|
||||||
"ID {0} | {1} | {2} | {3}",
|
|
||||||
timer.TimerHandle,
|
|
||||||
isLoop ? "Loop" : "Once",
|
|
||||||
isUnscaled ? "Unscaled" : "Scaled",
|
|
||||||
isRunning ? "Running" : "Paused");
|
|
||||||
|
|
||||||
string value = Utility.Text.Format(
|
|
||||||
"Left {0:F2}s | Duration {1:F2}s",
|
|
||||||
timer.LeftTime,
|
|
||||||
timer.Duration);
|
|
||||||
|
|
||||||
EditorGUILayout.LabelField(label, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
private void DrawStaleTimerList(ITimerDebugService timerDebugService, int activeCount)
|
||||||
private void DrawLeakDetection(ITimerService debug, int activeCount)
|
|
||||||
{
|
{
|
||||||
if (activeCount <= 0)
|
if (activeCount <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureLeakBuffer(activeCount);
|
if (!(timerDebugService is ITimerEditorDebugService editorDebugService))
|
||||||
int staleCount = debug.GetStaleOneShotTimers(_leakBuffer);
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int staleCount = editorDebugService.GetStaleOneShotTimers(_staleBuffer);
|
||||||
if (staleCount <= 0)
|
if (staleCount <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
EditorGUILayout.LabelField(Utility.Text.Format("Stale One-Shot Timers ({0})", staleCount), EditorStyles.boldLabel);
|
EditorGUILayout.HelpBox("Long-lived one-shot timers detected.", MessageType.Warning);
|
||||||
EditorGUILayout.HelpBox("Non-loop timers older than 5 minutes. This may indicate long-delay tasks or paused timers.", MessageType.Warning);
|
|
||||||
|
|
||||||
for (int i = 0; i < staleCount; i++)
|
for (int i = 0; i < staleCount; i++)
|
||||||
{
|
{
|
||||||
TimerDebugInfo staleTimer = _leakBuffer[i];
|
TimerDebugInfo info = _staleBuffer[i];
|
||||||
EditorGUILayout.LabelField(
|
EditorGUILayout.LabelField(Utility.Text.Format("ID {0}", info.TimerHandle), Utility.Text.Format("Age {0:F1}s | Left {1:F2}s", info.Age, info.LeftTime));
|
||||||
Utility.Text.Format("ID {0}", staleTimer.TimerHandle),
|
|
||||||
Utility.Text.Format("Created {0:F1}s ago", staleTimer.Age));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private void EnsureTimerBuffer(int count)
|
|
||||||
{
|
|
||||||
int capacity = count > 0 ? count : 1;
|
|
||||||
if (_timerBuffer == null || _timerBuffer.Length < capacity)
|
|
||||||
{
|
|
||||||
_timerBuffer = new TimerDebugInfo[capacity];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
private static void DrawTimerInfo(ref TimerDebugInfo info)
|
||||||
private void EnsureLeakBuffer(int count)
|
|
||||||
{
|
{
|
||||||
int capacity = count > 0 ? count : 1;
|
byte flags = info.Flags;
|
||||||
if (_leakBuffer == null || _leakBuffer.Length < capacity)
|
string mode = (flags & TimerDebugFlags.Loop) != 0 ? "Loop" : "Once";
|
||||||
{
|
string scale = (flags & TimerDebugFlags.Unscaled) != 0 ? "Unscaled" : "Scaled";
|
||||||
_leakBuffer = new TimerDebugInfo[capacity];
|
string state = (flags & TimerDebugFlags.Running) != 0 ? "Running" : "Paused";
|
||||||
}
|
EditorGUILayout.LabelField(
|
||||||
|
Utility.Text.Format("ID {0} | {1} | {2} | {3}", info.TimerHandle, mode, scale, state),
|
||||||
|
Utility.Text.Format("Left {0:F2}s | Duration {1:F2}s", info.LeftTime, info.Duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawStatistic(string label, int value)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField(label, value.ToString(), EditorStyles.boldLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawUsageBar(string label, int value, int capacity)
|
||||||
|
{
|
||||||
|
float ratio = capacity > 0 ? (float)value / capacity : 0f;
|
||||||
|
EditorGUILayout.Slider(label, ratio, 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int AlignCapacity(int value)
|
||||||
|
{
|
||||||
|
int aligned = ((value + CAPACITY_STEP - 1) / CAPACITY_STEP) * CAPACITY_STEP;
|
||||||
|
if (aligned < MIN_INITIAL_CAPACITY)
|
||||||
|
{
|
||||||
|
return MIN_INITIAL_CAPACITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return aligned > MAX_INITIAL_CAPACITY ? MAX_INITIAL_CAPACITY : aligned;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RequestRuntimeRepaint()
|
||||||
|
{
|
||||||
|
if (!EditorApplication.isPlaying)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentTime = EditorApplication.timeSinceStartup;
|
||||||
|
if (currentTime - _lastUpdateTime < UPDATE_INTERVAL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastUpdateTime = currentTime;
|
||||||
|
Repaint();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using AlicizaX.Editor;
|
using AlicizaX.Editor;
|
||||||
using AlicizaX.UI.Runtime;
|
using AlicizaX.UI.Runtime;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
@ -9,9 +9,26 @@ namespace AlicizaX.UI.Editor
|
|||||||
[CustomEditor(typeof(UIComponent))]
|
[CustomEditor(typeof(UIComponent))]
|
||||||
internal sealed class UIComponentInspector : GameFrameworkInspector
|
internal sealed class UIComponentInspector : GameFrameworkInspector
|
||||||
{
|
{
|
||||||
|
private const int CacheDebugInfoCapacity = 64;
|
||||||
|
private static readonly Color SectionColor = new Color(0.18f, 0.18f, 0.18f, 1f);
|
||||||
|
private static readonly Color OkColor = new Color(0.3f, 0.75f, 0.35f, 1f);
|
||||||
|
private static readonly Color WarningColor = new Color(1f, 0.72f, 0.18f, 1f);
|
||||||
|
private static readonly Color ErrorColor = new Color(1f, 0.32f, 0.28f, 1f);
|
||||||
|
private static readonly Color MutedColor = new Color(0.62f, 0.62f, 0.62f, 1f);
|
||||||
|
|
||||||
|
private readonly UIServiceDebugInfo _serviceInfo = new UIServiceDebugInfo();
|
||||||
|
private readonly UILayerDebugInfo _layerInfo = new UILayerDebugInfo();
|
||||||
|
private readonly UIWindowDebugInfo _windowInfo = new UIWindowDebugInfo();
|
||||||
|
private readonly UIWindowDebugInfo[] _cacheInfos = new UIWindowDebugInfo[CacheDebugInfoCapacity];
|
||||||
|
|
||||||
private SerializedProperty uiRoot;
|
private SerializedProperty uiRoot;
|
||||||
private SerializedProperty _isOrthographic;
|
private SerializedProperty _isOrthographic;
|
||||||
|
private bool _showRuntimeDebug = true;
|
||||||
|
private bool _showReferences;
|
||||||
|
private bool _showLayers = true;
|
||||||
|
private bool _showCache = true;
|
||||||
|
private bool _showEmptyLayers;
|
||||||
|
private Vector2 _runtimeScroll;
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
@ -27,7 +44,7 @@ namespace AlicizaX.UI.Editor
|
|||||||
|
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
|
||||||
GameObject rootPrefab = (GameObject)EditorGUILayout.ObjectField("UI根预设", uiRoot.objectReferenceValue, typeof(GameObject), false);
|
GameObject rootPrefab = (GameObject)EditorGUILayout.ObjectField("UI Root Prefab", uiRoot.objectReferenceValue, typeof(GameObject), false);
|
||||||
|
|
||||||
if (rootPrefab != uiRoot.objectReferenceValue)
|
if (rootPrefab != uiRoot.objectReferenceValue)
|
||||||
{
|
{
|
||||||
@ -36,7 +53,7 @@ namespace AlicizaX.UI.Editor
|
|||||||
|
|
||||||
if (uiRoot.objectReferenceValue == null)
|
if (uiRoot.objectReferenceValue == null)
|
||||||
{
|
{
|
||||||
if (GUILayout.Button("设置默认"))
|
if (GUILayout.Button("Set Default"))
|
||||||
{
|
{
|
||||||
GameObject defaultPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(UIGlobalPath.UIPrefabPath);
|
GameObject defaultPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(UIGlobalPath.UIPrefabPath);
|
||||||
uiRoot.objectReferenceValue = defaultPrefab;
|
uiRoot.objectReferenceValue = defaultPrefab;
|
||||||
@ -49,14 +66,283 @@ namespace AlicizaX.UI.Editor
|
|||||||
}
|
}
|
||||||
EditorGUI.EndDisabledGroup();
|
EditorGUI.EndDisabledGroup();
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
Repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
DrawRuntimeDebugInfo();
|
||||||
|
if (EditorApplication.isPlaying)
|
||||||
|
{
|
||||||
|
Repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
uiRoot = serializedObject.FindProperty("uiRoot");
|
uiRoot = serializedObject.FindProperty("uiRoot");
|
||||||
_isOrthographic = serializedObject.FindProperty("_isOrthographic");
|
_isOrthographic = serializedObject.FindProperty("_isOrthographic");
|
||||||
|
for (int i = 0; i < _cacheInfos.Length; i++)
|
||||||
|
{
|
||||||
|
_cacheInfos[i] = new UIWindowDebugInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRuntimeDebugInfo()
|
||||||
|
{
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
_showRuntimeDebug = EditorGUILayout.Foldout(_showRuntimeDebug, "Runtime Debug", true, EditorStyles.foldoutHeader);
|
||||||
|
if (!_showRuntimeDebug)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EditorApplication.isPlaying)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Enter Play Mode to inspect runtime UI state.", MessageType.Info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AppServices.TryGet<IUIService>(out IUIService uiService) || uiService is not IUIDebugService debugService)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("UI service is not initialized.", MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugService.FillServiceDebugInfo(_serviceInfo);
|
||||||
|
DrawServiceSummary();
|
||||||
|
DrawRuntimeOptions();
|
||||||
|
|
||||||
|
_runtimeScroll = EditorGUILayout.BeginScrollView(_runtimeScroll, GUILayout.MinHeight(260f), GUILayout.MaxHeight(720f));
|
||||||
|
DrawReferences();
|
||||||
|
DrawLayerDebugInfo(debugService);
|
||||||
|
DrawCacheDebugInfo(debugService);
|
||||||
|
EditorGUILayout.EndScrollView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRuntimeOptions()
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||||
|
_showLayers = GUILayout.Toggle(_showLayers, "Layers", EditorStyles.toolbarButton);
|
||||||
|
_showCache = GUILayout.Toggle(_showCache, "Cache", EditorStyles.toolbarButton);
|
||||||
|
_showReferences = GUILayout.Toggle(_showReferences, "References", EditorStyles.toolbarButton);
|
||||||
|
_showEmptyLayers = GUILayout.Toggle(_showEmptyLayers, "Empty Layers", EditorStyles.toolbarButton);
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawServiceSummary()
|
||||||
|
{
|
||||||
|
DrawSectionBegin("Service Summary");
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
DrawCounter("Initialized", _serviceInfo.Initialized ? "Yes" : "No", _serviceInfo.Initialized ? OkColor : ErrorColor);
|
||||||
|
DrawCounter("Mode", _serviceInfo.Orthographic ? "Orthographic" : "Perspective", Color.white);
|
||||||
|
DrawCounter("Layers", _serviceInfo.LayerCount.ToString(), Color.white);
|
||||||
|
DrawCounter("Open", _serviceInfo.OpenWindowCount.ToString(), _serviceInfo.OpenWindowCount > 0 ? OkColor : MutedColor);
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
DrawCounter("Cache", _serviceInfo.CacheWindowCount.ToString(), _serviceInfo.CacheWindowCount > 0 ? WarningColor : MutedColor);
|
||||||
|
DrawCounter("Update", _serviceInfo.UpdateWindowCount.ToString(), _serviceInfo.UpdateWindowCount > 0 ? OkColor : MutedColor);
|
||||||
|
DrawCounter("Block", _serviceInfo.BlockActive ? "Active" : "Inactive", _serviceInfo.BlockActive ? WarningColor : MutedColor);
|
||||||
|
DrawCounter("Block Timer", _serviceInfo.BlockTimerHandle.ToString(), _serviceInfo.BlockTimerHandle != 0UL ? WarningColor : MutedColor);
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
DrawSectionEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawReferences()
|
||||||
|
{
|
||||||
|
if (!_showReferences)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawSectionBegin("References");
|
||||||
|
EditorGUILayout.ObjectField("Root", _serviceInfo.Root, typeof(Transform), true);
|
||||||
|
EditorGUILayout.ObjectField("Canvas Root", _serviceInfo.CanvasRoot, typeof(Transform), true);
|
||||||
|
EditorGUILayout.ObjectField("Canvas", _serviceInfo.Canvas, typeof(Canvas), true);
|
||||||
|
EditorGUILayout.ObjectField("Camera", _serviceInfo.Camera, typeof(Camera), true);
|
||||||
|
DrawSectionEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawLayerDebugInfo(IUIDebugService debugService)
|
||||||
|
{
|
||||||
|
if (!_showLayers)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawSectionBegin("Open Windows");
|
||||||
|
bool hasWindow = false;
|
||||||
|
for (int layerIndex = 0; layerIndex < debugService.LayerCount; layerIndex++)
|
||||||
|
{
|
||||||
|
if (!debugService.FillLayerDebugInfo(layerIndex, _layerInfo))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_layerInfo.WindowCount == 0 && !_showEmptyLayers)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasWindow |= _layerInfo.WindowCount > 0;
|
||||||
|
DrawLayerHeader(_layerInfo);
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
for (int windowIndex = 0; windowIndex < _layerInfo.WindowCount; windowIndex++)
|
||||||
|
{
|
||||||
|
if (debugService.FillWindowDebugInfo(layerIndex, windowIndex, _windowInfo))
|
||||||
|
{
|
||||||
|
DrawWindowDebugInfo(_windowInfo, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasWindow && !_showEmptyLayers)
|
||||||
|
{
|
||||||
|
DrawEmptyLabel("No open UI windows.");
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawSectionEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCacheDebugInfo(IUIDebugService debugService)
|
||||||
|
{
|
||||||
|
if (!_showCache)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawSectionBegin("Cached Windows");
|
||||||
|
int count = debugService.FillCacheDebugInfo(_cacheInfos, _cacheInfos.Length);
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
DrawEmptyLabel("No cached UI windows.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
DrawWindowDebugInfo(_cacheInfos[i], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debugService.CacheWindowCount > _cacheInfos.Length)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Cache list is truncated by inspector buffer capacity.", MessageType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawSectionEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawLayerHeader(UILayerDebugInfo info)
|
||||||
|
{
|
||||||
|
Rect rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight + 4f);
|
||||||
|
EditorGUI.DrawRect(rect, SectionColor);
|
||||||
|
rect.x += 6f;
|
||||||
|
rect.width -= 12f;
|
||||||
|
rect.y += 2f;
|
||||||
|
EditorGUI.LabelField(
|
||||||
|
rect,
|
||||||
|
info.Layer.ToString(),
|
||||||
|
"Windows " + info.WindowCount + " | Last Fullscreen " + info.LastFullscreenIndex,
|
||||||
|
EditorStyles.boldLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawWindowDebugInfo(UIWindowDebugInfo info, bool cached)
|
||||||
|
{
|
||||||
|
Rect rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight + 4f);
|
||||||
|
Color color = GetWindowColor(info, cached);
|
||||||
|
EditorGUI.DrawRect(new Rect(rect.x, rect.y + 2f, 4f, rect.height - 4f), color);
|
||||||
|
rect.x += 8f;
|
||||||
|
rect.width -= 8f;
|
||||||
|
EditorGUI.ObjectField(rect, GetWindowTitle(info, cached), info.HolderTransform, typeof(Transform), true);
|
||||||
|
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
EditorGUILayout.LabelField("State", GetStateLine(info));
|
||||||
|
EditorGUILayout.LabelField("Flags", GetFlagLine(info));
|
||||||
|
if (cached || info.CacheTime > 0f || info.CacheTimerHandle != 0UL)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("Cache", GetCacheLine(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.LabelField("Holder", string.IsNullOrEmpty(info.HolderTypeName) ? "None" : info.HolderTypeName);
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetWindowTitle(UIWindowDebugInfo info, bool cached)
|
||||||
|
{
|
||||||
|
string logicName = string.IsNullOrEmpty(info.LogicTypeName) ? "Unknown" : info.LogicTypeName;
|
||||||
|
string prefix = cached ? "[Cache] " : "";
|
||||||
|
return prefix + "#" + info.OrderIndex + " L" + info.LayerIndex + " " + logicName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetStateLine(UIWindowDebugInfo info)
|
||||||
|
{
|
||||||
|
return info.State + " | Visible " + info.Visible + " | Depth " + info.Depth + " | FullScreen " + info.FullScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetFlagLine(UIWindowDebugInfo info)
|
||||||
|
{
|
||||||
|
return "Update " + info.NeedUpdate + " | InCache " + info.InCache + " | ShowOp " + info.ShowInProgress + " | CloseOp " + info.CloseInProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCacheLine(UIWindowDebugInfo info)
|
||||||
|
{
|
||||||
|
return "Time " + info.CacheTime.ToString("F2") + " | Timer " + info.CacheTimerHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color GetWindowColor(UIWindowDebugInfo info, bool cached)
|
||||||
|
{
|
||||||
|
if (info.ShowInProgress || info.CloseInProgress)
|
||||||
|
{
|
||||||
|
return WarningColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached || info.InCache)
|
||||||
|
{
|
||||||
|
return MutedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.Visible)
|
||||||
|
{
|
||||||
|
return ErrorColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return info.FullScreen ? OkColor : Color.cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawCounter(string label, string value, Color valueColor)
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.MinWidth(82f));
|
||||||
|
EditorGUILayout.LabelField(label, EditorStyles.miniLabel);
|
||||||
|
Color oldColor = GUI.color;
|
||||||
|
GUI.color = valueColor;
|
||||||
|
EditorGUILayout.LabelField(value, EditorStyles.boldLabel);
|
||||||
|
GUI.color = oldColor;
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawSectionBegin(string title)
|
||||||
|
{
|
||||||
|
EditorGUILayout.Space(4f);
|
||||||
|
Rect rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight + 6f);
|
||||||
|
EditorGUI.DrawRect(rect, SectionColor);
|
||||||
|
rect.x += 6f;
|
||||||
|
rect.y += 3f;
|
||||||
|
rect.width -= 12f;
|
||||||
|
EditorGUI.LabelField(rect, title, EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawSectionEnd()
|
||||||
|
{
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawEmptyLabel(string text)
|
||||||
|
{
|
||||||
|
Color oldColor = GUI.color;
|
||||||
|
GUI.color = MutedColor;
|
||||||
|
EditorGUILayout.LabelField(text, EditorStyles.miniLabel);
|
||||||
|
GUI.color = oldColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,4 +26,11 @@ namespace AlicizaX
|
|||||||
{
|
{
|
||||||
public int Order { get; }
|
public int Order { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal enum ServiceScopeKind : byte
|
||||||
|
{
|
||||||
|
App = 0,
|
||||||
|
Scene = 1,
|
||||||
|
Gameplay = 2,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,8 +21,12 @@ namespace AlicizaX
|
|||||||
return _world;
|
return _world;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T RegisterApp<T>(T service, params Type[] extraContracts) where T : class, IService
|
public static T RegisterAppSelf<T>(T service) where T : class, IService
|
||||||
=> RequireWorld().App.Register(service, extraContracts);
|
=> RequireWorld().App.RegisterSelf(service);
|
||||||
|
|
||||||
|
public static TContract RegisterApp<TContract>(IService service)
|
||||||
|
where TContract : class, IService
|
||||||
|
=> RequireWorld().App.Register<TContract>(service);
|
||||||
|
|
||||||
public static bool TryGetApp<T>(out T service) where T : class, IService
|
public static bool TryGetApp<T>(out T service) where T : class, IService
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
using System;
|
namespace AlicizaX
|
||||||
|
|
||||||
namespace AlicizaX
|
|
||||||
{
|
{
|
||||||
public interface IServiceRegistry
|
public interface IServiceRegistry
|
||||||
{
|
{
|
||||||
T Register<T>(T service, params Type[] extraContracts) where T : class, IService;
|
T RegisterSelf<T>(T service) where T : class, IService;
|
||||||
|
TContract Register<TContract>(IService service) where TContract : class, IService;
|
||||||
bool TryGet<T>(out T service) where T : class, IService;
|
bool TryGet<T>(out T service) where T : class, IService;
|
||||||
T Require<T>() where T : class, IService;
|
T Require<T>() where T : class, IService;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
using Cysharp.Text;
|
||||||
|
|
||||||
namespace AlicizaX
|
namespace AlicizaX
|
||||||
{
|
{
|
||||||
internal interface IServiceLifecycle
|
internal interface IServiceLifecycle
|
||||||
@ -15,7 +17,7 @@ namespace AlicizaX
|
|||||||
void IServiceLifecycle.Initialize(ServiceContext context)
|
void IServiceLifecycle.Initialize(ServiceContext context)
|
||||||
{
|
{
|
||||||
if (IsInitialized)
|
if (IsInitialized)
|
||||||
throw new System.InvalidOperationException($"{GetType().FullName} is already initialized.");
|
throw new System.InvalidOperationException(ZString.Format("{0} is already initialized.", GetType().FullName));
|
||||||
|
|
||||||
Context = context;
|
Context = context;
|
||||||
IsInitialized = true;
|
IsInitialized = true;
|
||||||
|
|||||||
@ -1,78 +1,37 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace AlicizaX
|
namespace AlicizaX
|
||||||
{
|
{
|
||||||
internal static class ServiceContractUtility
|
internal static class ServiceContractUtility
|
||||||
{
|
{
|
||||||
private static readonly HashSet<Type> ExcludedContracts = new HashSet<Type>
|
public static ServiceContracts Create(Type serviceType)
|
||||||
|
=> new ServiceContracts(serviceType, null);
|
||||||
|
|
||||||
|
public static ServiceContracts Create(Type serviceType, Type contractType)
|
||||||
|
=> new ServiceContracts(serviceType, contractType);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly struct ServiceContracts
|
||||||
|
{
|
||||||
|
private readonly Type _serviceType;
|
||||||
|
private readonly Type _contractType;
|
||||||
|
|
||||||
|
public ServiceContracts(Type serviceType, Type contractType)
|
||||||
{
|
{
|
||||||
typeof(IService),
|
_serviceType = serviceType;
|
||||||
typeof(IMonoService),
|
_contractType = contractType;
|
||||||
typeof(IServiceTickable),
|
|
||||||
typeof(IServiceLateTickable),
|
|
||||||
typeof(IServiceFixedTickable),
|
|
||||||
typeof(IServiceGizmoDrawable),
|
|
||||||
typeof(IServiceOrder),
|
|
||||||
typeof(IServiceLifecycle),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cache for the common no-extraContracts path — contracts per concrete type never change.
|
|
||||||
private static readonly Dictionary<Type, List<Type>> _contractCache = new Dictionary<Type, List<Type>>();
|
|
||||||
|
|
||||||
public static List<Type> Collect(Type serviceType, IReadOnlyList<Type> extraContracts)
|
|
||||||
{
|
|
||||||
if (extraContracts == null || extraContracts.Count == 0)
|
|
||||||
{
|
|
||||||
if (_contractCache.TryGetValue(serviceType, out var cached))
|
|
||||||
return cached;
|
|
||||||
var result = BuildContracts(serviceType);
|
|
||||||
_contractCache[serviceType] = result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extra contracts path: build fresh, validate, append extras.
|
|
||||||
var contracts = BuildContracts(serviceType);
|
|
||||||
contracts = new List<Type>(contracts); // don't mutate the cached list
|
|
||||||
var unique = new HashSet<Type>(contracts);
|
|
||||||
for (var i = 0; i < extraContracts.Count; i++)
|
|
||||||
{
|
|
||||||
var extraContract = extraContracts[i];
|
|
||||||
ValidateExtraContract(serviceType, extraContract);
|
|
||||||
if (unique.Add(extraContract)) contracts.Add(extraContract);
|
|
||||||
}
|
|
||||||
return contracts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Type> BuildContracts(Type serviceType)
|
public int Count => _contractType == null || _contractType == _serviceType ? 1 : 2;
|
||||||
{
|
|
||||||
var contracts = new List<Type> { serviceType };
|
|
||||||
var unique = new HashSet<Type> { serviceType };
|
|
||||||
|
|
||||||
var interfaces = serviceType.GetInterfaces();
|
public Type this[int index]
|
||||||
for (var i = 0; i < interfaces.Length; i++)
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
var contract = interfaces[i];
|
if (index == 0) return _serviceType;
|
||||||
if (!typeof(IService).IsAssignableFrom(contract)) continue;
|
if (index == 1 && Count == 2) return _contractType;
|
||||||
if (ExcludedContracts.Contains(contract)) continue;
|
throw new IndexOutOfRangeException();
|
||||||
if (unique.Add(contract)) contracts.Add(contract);
|
|
||||||
}
|
}
|
||||||
return contracts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ValidateExtraContract(Type serviceType, Type contract)
|
|
||||||
{
|
|
||||||
if (contract == null)
|
|
||||||
throw new ArgumentNullException(nameof(contract));
|
|
||||||
|
|
||||||
if (!typeof(IService).IsAssignableFrom(contract))
|
|
||||||
throw new InvalidOperationException($"{contract.FullName} must inherit {nameof(IService)}.");
|
|
||||||
|
|
||||||
if (!contract.IsAssignableFrom(serviceType))
|
|
||||||
throw new InvalidOperationException($"{serviceType.FullName} does not implement {contract.FullName}.");
|
|
||||||
|
|
||||||
if (ExcludedContracts.Contains(contract))
|
|
||||||
throw new InvalidOperationException($"{contract.FullName} cannot be used as a service contract.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Cysharp.Text;
|
||||||
|
|
||||||
namespace AlicizaX
|
namespace AlicizaX
|
||||||
{
|
{
|
||||||
internal sealed class ServiceScope : IDisposable, IServiceRegistry
|
internal sealed class ServiceScope : IDisposable, IServiceRegistry
|
||||||
{
|
{
|
||||||
private readonly Dictionary<Type, IService> _servicesByContract = new Dictionary<Type, IService>();
|
private const int MissingIndex = -1;
|
||||||
private readonly Dictionary<IService, List<Type>> _contractsByService = new Dictionary<IService, List<Type>>(ReferenceComparer<IService>.Instance);
|
|
||||||
|
private readonly Dictionary<RuntimeTypeHandle, IService> _servicesByContract = new Dictionary<RuntimeTypeHandle, IService>();
|
||||||
|
private readonly Dictionary<IService, ServiceEntry> _entriesByService = new Dictionary<IService, ServiceEntry>(ReferenceComparer<IService>.Instance);
|
||||||
private readonly List<IService> _registrationOrder = new List<IService>();
|
private readonly List<IService> _registrationOrder = new List<IService>();
|
||||||
|
|
||||||
private readonly List<IServiceTickable> _tickables = new List<IServiceTickable>();
|
private readonly List<IServiceTickable> _tickables = new List<IServiceTickable>();
|
||||||
@ -14,101 +17,74 @@ namespace AlicizaX
|
|||||||
private readonly List<IServiceFixedTickable> _fixedTickables = new List<IServiceFixedTickable>();
|
private readonly List<IServiceFixedTickable> _fixedTickables = new List<IServiceFixedTickable>();
|
||||||
private readonly List<IServiceGizmoDrawable> _gizmoDrawables = new List<IServiceGizmoDrawable>();
|
private readonly List<IServiceGizmoDrawable> _gizmoDrawables = new List<IServiceGizmoDrawable>();
|
||||||
|
|
||||||
private IServiceTickable[] _tickableSnapshot = Array.Empty<IServiceTickable>();
|
private readonly List<PendingChange> _pendingChanges = new List<PendingChange>();
|
||||||
private IServiceLateTickable[] _lateTickableSnapshot = Array.Empty<IServiceLateTickable>();
|
|
||||||
private IServiceFixedTickable[] _fixedTickableSnapshot = Array.Empty<IServiceFixedTickable>();
|
|
||||||
private IServiceGizmoDrawable[] _gizmoSnapshot = Array.Empty<IServiceGizmoDrawable>();
|
|
||||||
|
|
||||||
private bool _tickablesDirty;
|
private bool _tickablesDirty;
|
||||||
private bool _lateTickablesDirty;
|
private bool _lateTickablesDirty;
|
||||||
private bool _fixedTickablesDirty;
|
private bool _fixedTickablesDirty;
|
||||||
private bool _gizmoDrawablesDirty;
|
private bool _gizmoDrawablesDirty;
|
||||||
|
private bool _isIterating;
|
||||||
|
|
||||||
internal ServiceScope(ServiceWorld world, string name, int order)
|
internal ServiceScope(ServiceWorld world, ServiceScopeKind kind, string name, int order, int creationIndex)
|
||||||
{
|
{
|
||||||
World = world ?? throw new ArgumentNullException(nameof(world));
|
World = world ?? throw new ArgumentNullException(nameof(world));
|
||||||
|
Kind = kind;
|
||||||
Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentException("Scope name cannot be empty.", nameof(name)) : name;
|
Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentException("Scope name cannot be empty.", nameof(name)) : name;
|
||||||
Order = order;
|
Order = order;
|
||||||
|
CreationIndex = creationIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ServiceWorld World { get; }
|
internal ServiceWorld World { get; }
|
||||||
|
|
||||||
|
internal ServiceScopeKind Kind { get; }
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
internal int Order { get; }
|
internal int Order { get; }
|
||||||
|
|
||||||
|
internal int CreationIndex { get; }
|
||||||
|
|
||||||
internal bool IsDisposed { get; private set; }
|
internal bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
public T Register<T>(T service, params Type[] extraContracts)
|
public T RegisterSelf<T>(T service) where T : class, IService
|
||||||
where T : class, IService
|
=> RegisterInternal(service, ServiceContractUtility.Create(service != null ? service.GetType() : typeof(T)));
|
||||||
{
|
|
||||||
EnsureNotDisposed();
|
|
||||||
|
|
||||||
if (service == null)
|
public TContract Register<TContract>(IService service)
|
||||||
throw new ArgumentNullException(nameof(service));
|
where TContract : class, IService
|
||||||
|
=> RegisterInternal(service, ServiceContractUtility.Create(service != null ? service.GetType() : typeof(TContract), typeof(TContract))) as TContract;
|
||||||
if (service is not IServiceLifecycle lifecycle)
|
|
||||||
throw new InvalidOperationException($"Service {service.GetType().FullName} must implement {nameof(IServiceLifecycle)}.");
|
|
||||||
|
|
||||||
ValidateService(service);
|
|
||||||
|
|
||||||
if (_contractsByService.ContainsKey(service))
|
|
||||||
throw new InvalidOperationException($"Service {service.GetType().FullName} is already registered in scope {Name}.");
|
|
||||||
|
|
||||||
var contracts = ServiceContractUtility.Collect(service.GetType(), extraContracts);
|
|
||||||
for (var i = 0; i < contracts.Count; i++)
|
|
||||||
{
|
|
||||||
var contract = contracts[i];
|
|
||||||
if (_servicesByContract.TryGetValue(contract, out var existing))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Scope {Name} already contains contract {contract.FullName} bound to {existing.GetType().FullName}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_contractsByService.Add(service, contracts);
|
|
||||||
_registrationOrder.Add(service);
|
|
||||||
for (var i = 0; i < contracts.Count; i++)
|
|
||||||
_servicesByContract.Add(contracts[i], service);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
lifecycle.Initialize(new ServiceContext(World, this));
|
|
||||||
AddToLifecycleLists(service);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
RemoveBindings(service);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Unregister<T>() where T : class, IService
|
public bool Unregister<T>() where T : class, IService
|
||||||
{
|
{
|
||||||
if (!_servicesByContract.TryGetValue(typeof(T), out var service))
|
if (!_servicesByContract.TryGetValue(typeof(T).TypeHandle, out var service))
|
||||||
return false;
|
return false;
|
||||||
return Unregister(service);
|
return Unregister(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Unregister(IService service)
|
public bool Unregister(IService service)
|
||||||
{
|
{
|
||||||
if (service == null || !_contractsByService.ContainsKey(service))
|
if (service == null || !_entriesByService.TryGetValue(service, out var entry))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (service is not IServiceLifecycle lifecycle)
|
if (service is not IServiceLifecycle lifecycle)
|
||||||
throw new InvalidOperationException($"Service {service.GetType().FullName} must implement {nameof(IServiceLifecycle)}.");
|
throw new InvalidOperationException(ZString.Format("Service {0} must implement {1}.", service.GetType().FullName, nameof(IServiceLifecycle)));
|
||||||
|
|
||||||
RemoveFromLifecycleLists(service);
|
if (_isIterating)
|
||||||
RemoveBindings(service);
|
{
|
||||||
|
if (entry.PendingRemove) return true;
|
||||||
|
entry.PendingRemove = true;
|
||||||
|
_entriesByService[service] = entry;
|
||||||
|
_pendingChanges.Add(PendingChange.Remove(service));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveEntry(service, entry, true);
|
||||||
lifecycle.Destroy();
|
lifecycle.Destroy();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGet<T>(out T service) where T : class, IService
|
public bool TryGet<T>(out T service) where T : class, IService
|
||||||
{
|
{
|
||||||
if (_servicesByContract.TryGetValue(typeof(T), out var raw))
|
if (_servicesByContract.TryGetValue(typeof(T).TypeHandle, out var raw))
|
||||||
{
|
{
|
||||||
service = raw as T;
|
service = raw as T;
|
||||||
return service != null;
|
return service != null;
|
||||||
@ -118,60 +94,180 @@ namespace AlicizaX
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool TryGet(Type contract, out IService service)
|
||||||
|
=> _servicesByContract.TryGetValue(contract.TypeHandle, out service);
|
||||||
|
|
||||||
public T Require<T>() where T : class, IService
|
public T Require<T>() where T : class, IService
|
||||||
{
|
{
|
||||||
if (TryGet(out T service)) return service;
|
if (TryGet(out T service)) return service;
|
||||||
Log.Error($"Scope {Name} does not contain service {typeof(T).FullName}.");
|
throw new InvalidOperationException(ZString.Format("Scope {0} does not contain service {1}.", Name, typeof(T).FullName));
|
||||||
return default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasContract(Type contractType)
|
public bool HasContract(Type contractType)
|
||||||
=> _servicesByContract.ContainsKey(contractType);
|
=> _servicesByContract.ContainsKey(contractType.TypeHandle);
|
||||||
|
|
||||||
internal void Tick(float deltaTime)
|
internal void Tick(float deltaTime)
|
||||||
{
|
{
|
||||||
var snapshot = GetTickSnapshot();
|
SortTickablesIfDirty();
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].Tick(deltaTime);
|
_isIterating = true;
|
||||||
|
for (var i = 0; i < _tickables.Count; i++) _tickables[i].Tick(deltaTime);
|
||||||
|
_isIterating = false;
|
||||||
|
FlushPendingChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void LateTick(float deltaTime)
|
internal void LateTick(float deltaTime)
|
||||||
{
|
{
|
||||||
var snapshot = GetLateTickSnapshot();
|
SortLateTickablesIfDirty();
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].LateTick(deltaTime);
|
_isIterating = true;
|
||||||
|
for (var i = 0; i < _lateTickables.Count; i++) _lateTickables[i].LateTick(deltaTime);
|
||||||
|
_isIterating = false;
|
||||||
|
FlushPendingChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void FixedTick(float fixedDeltaTime)
|
internal void FixedTick(float fixedDeltaTime)
|
||||||
{
|
{
|
||||||
var snapshot = GetFixedTickSnapshot();
|
SortFixedTickablesIfDirty();
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].FixedTick(fixedDeltaTime);
|
_isIterating = true;
|
||||||
|
for (var i = 0; i < _fixedTickables.Count; i++) _fixedTickables[i].FixedTick(fixedDeltaTime);
|
||||||
|
_isIterating = false;
|
||||||
|
FlushPendingChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DrawGizmos()
|
internal void DrawGizmos()
|
||||||
{
|
{
|
||||||
var snapshot = GetGizmoSnapshot();
|
SortGizmoDrawablesIfDirty();
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].DrawGizmos();
|
_isIterating = true;
|
||||||
|
for (var i = 0; i < _gizmoDrawables.Count; i++) _gizmoDrawables[i].DrawGizmos();
|
||||||
|
_isIterating = false;
|
||||||
|
FlushPendingChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (IsDisposed) return;
|
if (IsDisposed) return;
|
||||||
|
|
||||||
var snapshot = _registrationOrder.ToArray();
|
_isIterating = false;
|
||||||
for (var i = snapshot.Length - 1; i >= 0; i--)
|
_pendingChanges.Clear();
|
||||||
|
|
||||||
|
for (var i = _registrationOrder.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var service = snapshot[i];
|
var service = _registrationOrder[i];
|
||||||
if (!_contractsByService.ContainsKey(service)) continue;
|
if (service == null || !_entriesByService.TryGetValue(service, out var entry)) continue;
|
||||||
if (service is not IServiceLifecycle lifecycle)
|
if (service is not IServiceLifecycle lifecycle)
|
||||||
throw new InvalidOperationException($"Service {service.GetType().FullName} must implement {nameof(IServiceLifecycle)}.");
|
throw new InvalidOperationException(ZString.Format("Service {0} must implement {1}.", service.GetType().FullName, nameof(IServiceLifecycle)));
|
||||||
RemoveFromLifecycleLists(service);
|
|
||||||
RemoveContractBindings(service); // skip _registrationOrder.Remove — we clear below
|
RemoveEntry(service, entry, false);
|
||||||
lifecycle.Destroy();
|
lifecycle.Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
_registrationOrder.Clear();
|
_registrationOrder.Clear();
|
||||||
|
_tickables.Clear();
|
||||||
|
_lateTickables.Clear();
|
||||||
|
_fixedTickables.Clear();
|
||||||
|
_gizmoDrawables.Clear();
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private T RegisterInternal<T>(T service, ServiceContracts contracts) where T : class, IService
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
|
||||||
|
if (service == null)
|
||||||
|
throw new ArgumentNullException(nameof(service));
|
||||||
|
|
||||||
|
if (service is not IServiceLifecycle lifecycle)
|
||||||
|
throw new InvalidOperationException(ZString.Format("Service {0} must implement {1}.", service.GetType().FullName, nameof(IServiceLifecycle)));
|
||||||
|
|
||||||
|
ValidateService(service);
|
||||||
|
|
||||||
|
if (_entriesByService.ContainsKey(service))
|
||||||
|
throw new InvalidOperationException(ZString.Format("Service {0} is already registered in scope {1}.", service.GetType().FullName, Name));
|
||||||
|
|
||||||
|
if (_isIterating)
|
||||||
|
{
|
||||||
|
ValidateContracts(contracts);
|
||||||
|
_pendingChanges.Add(PendingChange.Add(service, contracts));
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidateContracts(contracts);
|
||||||
|
lifecycle.Initialize(new ServiceContext(World, this));
|
||||||
|
AddEntry(service, contracts);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateContracts(ServiceContracts contracts)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < contracts.Count; i++)
|
||||||
|
{
|
||||||
|
var contract = contracts[i];
|
||||||
|
if (_servicesByContract.TryGetValue(contract.TypeHandle, out var existing))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
ZString.Format("Scope {0} already contains contract {1} bound to {2}.", Name, contract.FullName, existing.GetType().FullName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _pendingChanges.Count; i++)
|
||||||
|
{
|
||||||
|
var change = _pendingChanges[i];
|
||||||
|
if (!change.IsAdd) continue;
|
||||||
|
for (var contractIndex = 0; contractIndex < contracts.Count; contractIndex++)
|
||||||
|
{
|
||||||
|
var contract = contracts[contractIndex];
|
||||||
|
for (var pendingContractIndex = 0; pendingContractIndex < change.Contracts.Count; pendingContractIndex++)
|
||||||
|
{
|
||||||
|
if (!change.Contracts[pendingContractIndex].TypeHandle.Equals(contract.TypeHandle)) continue;
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
ZString.Format("Scope {0} already contains pending contract {1}.", Name, contract.FullName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddEntry(IService service, ServiceContracts contracts)
|
||||||
|
{
|
||||||
|
var entry = new ServiceEntry(contracts, _registrationOrder.Count);
|
||||||
|
_registrationOrder.Add(service);
|
||||||
|
|
||||||
|
for (var i = 0; i < contracts.Count; i++)
|
||||||
|
{
|
||||||
|
var contract = contracts[i];
|
||||||
|
_servicesByContract.Add(contract.TypeHandle, service);
|
||||||
|
World.AddContract(this, contract, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddToLifecycleLists(service, ref entry);
|
||||||
|
_entriesByService.Add(service, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveEntry(IService service, ServiceEntry entry, bool removeRegistrationOrder)
|
||||||
|
{
|
||||||
|
_entriesByService.Remove(service);
|
||||||
|
RemoveFromLifecycleLists(service, entry);
|
||||||
|
|
||||||
|
for (var i = 0; i < entry.Contracts.Count; i++)
|
||||||
|
{
|
||||||
|
var contract = entry.Contracts[i];
|
||||||
|
_servicesByContract.Remove(contract.TypeHandle);
|
||||||
|
World.RemoveContract(this, contract, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!removeRegistrationOrder) return;
|
||||||
|
|
||||||
|
var lastIndex = _registrationOrder.Count - 1;
|
||||||
|
var removedIndex = entry.RegistrationIndex;
|
||||||
|
var lastService = _registrationOrder[lastIndex];
|
||||||
|
_registrationOrder[removedIndex] = lastService;
|
||||||
|
_registrationOrder.RemoveAt(lastIndex);
|
||||||
|
|
||||||
|
if (removedIndex != lastIndex && lastService != null && _entriesByService.TryGetValue(lastService, out var lastEntry))
|
||||||
|
{
|
||||||
|
lastEntry.RegistrationIndex = removedIndex;
|
||||||
|
_entriesByService[lastService] = lastEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureNotDisposed()
|
private void EnsureNotDisposed()
|
||||||
{
|
{
|
||||||
if (IsDisposed) throw new ObjectDisposedException(Name);
|
if (IsDisposed) throw new ObjectDisposedException(Name);
|
||||||
@ -186,115 +282,202 @@ namespace AlicizaX
|
|||||||
service is IServiceGizmoDrawable))
|
service is IServiceGizmoDrawable))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"Mono service {service.GetType().FullName} cannot implement tick lifecycle interfaces.");
|
ZString.Format("Mono service {0} cannot implement tick lifecycle interfaces.", service.GetType().FullName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddToLifecycleLists(IService service)
|
private void AddToLifecycleLists(IService service, ref ServiceEntry entry)
|
||||||
{
|
{
|
||||||
if (service is IServiceTickable tickable)
|
if (service is IServiceTickable tickable)
|
||||||
{
|
{
|
||||||
|
entry.TickIndex = _tickables.Count;
|
||||||
_tickables.Add(tickable);
|
_tickables.Add(tickable);
|
||||||
_tickablesDirty = true;
|
_tickablesDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (service is IServiceLateTickable late)
|
if (service is IServiceLateTickable late)
|
||||||
{
|
{
|
||||||
|
entry.LateTickIndex = _lateTickables.Count;
|
||||||
_lateTickables.Add(late);
|
_lateTickables.Add(late);
|
||||||
_lateTickablesDirty = true;
|
_lateTickablesDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (service is IServiceFixedTickable fixed_)
|
if (service is IServiceFixedTickable fixed_)
|
||||||
{
|
{
|
||||||
|
entry.FixedTickIndex = _fixedTickables.Count;
|
||||||
_fixedTickables.Add(fixed_);
|
_fixedTickables.Add(fixed_);
|
||||||
_fixedTickablesDirty = true;
|
_fixedTickablesDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (service is IServiceGizmoDrawable gizmo)
|
if (service is IServiceGizmoDrawable gizmo)
|
||||||
{
|
{
|
||||||
|
entry.GizmoIndex = _gizmoDrawables.Count;
|
||||||
_gizmoDrawables.Add(gizmo);
|
_gizmoDrawables.Add(gizmo);
|
||||||
_gizmoDrawablesDirty = true;
|
_gizmoDrawablesDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveFromLifecycleLists(IService service)
|
private void RemoveFromLifecycleLists(IService service, ServiceEntry entry)
|
||||||
{
|
{
|
||||||
if (service is IServiceTickable tickable && _tickables.Remove(tickable)) _tickablesDirty = true;
|
if (entry.TickIndex != MissingIndex) RemoveTickableAt(entry.TickIndex);
|
||||||
if (service is IServiceLateTickable late && _lateTickables.Remove(late)) _lateTickablesDirty = true;
|
if (entry.LateTickIndex != MissingIndex) RemoveLateTickableAt(entry.LateTickIndex);
|
||||||
if (service is IServiceFixedTickable fixed_ && _fixedTickables.Remove(fixed_)) _fixedTickablesDirty = true;
|
if (entry.FixedTickIndex != MissingIndex) RemoveFixedTickableAt(entry.FixedTickIndex);
|
||||||
if (service is IServiceGizmoDrawable gizmo && _gizmoDrawables.Remove(gizmo)) _gizmoDrawablesDirty = true;
|
if (entry.GizmoIndex != MissingIndex) RemoveGizmoDrawableAt(entry.GizmoIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveBindings(IService service)
|
private void RemoveTickableAt(int index)
|
||||||
{
|
{
|
||||||
if (_contractsByService.TryGetValue(service, out var contracts))
|
var lastIndex = _tickables.Count - 1;
|
||||||
|
var moved = _tickables[lastIndex];
|
||||||
|
_tickables[index] = moved;
|
||||||
|
_tickables.RemoveAt(lastIndex);
|
||||||
|
if (index != lastIndex) UpdateTickIndex(moved, index);
|
||||||
|
_tickablesDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveLateTickableAt(int index)
|
||||||
|
{
|
||||||
|
var lastIndex = _lateTickables.Count - 1;
|
||||||
|
var moved = _lateTickables[lastIndex];
|
||||||
|
_lateTickables[index] = moved;
|
||||||
|
_lateTickables.RemoveAt(lastIndex);
|
||||||
|
if (index != lastIndex) UpdateLateTickIndex(moved, index);
|
||||||
|
_lateTickablesDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveFixedTickableAt(int index)
|
||||||
|
{
|
||||||
|
var lastIndex = _fixedTickables.Count - 1;
|
||||||
|
var moved = _fixedTickables[lastIndex];
|
||||||
|
_fixedTickables[index] = moved;
|
||||||
|
_fixedTickables.RemoveAt(lastIndex);
|
||||||
|
if (index != lastIndex) UpdateFixedTickIndex(moved, index);
|
||||||
|
_fixedTickablesDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveGizmoDrawableAt(int index)
|
||||||
|
{
|
||||||
|
var lastIndex = _gizmoDrawables.Count - 1;
|
||||||
|
var moved = _gizmoDrawables[lastIndex];
|
||||||
|
_gizmoDrawables[index] = moved;
|
||||||
|
_gizmoDrawables.RemoveAt(lastIndex);
|
||||||
|
if (index != lastIndex) UpdateGizmoIndex(moved, index);
|
||||||
|
_gizmoDrawablesDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTickIndex(IServiceTickable service, int index)
|
||||||
|
{
|
||||||
|
if (!_entriesByService.TryGetValue((IService)service, out var entry)) return;
|
||||||
|
entry.TickIndex = index;
|
||||||
|
_entriesByService[(IService)service] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateLateTickIndex(IServiceLateTickable service, int index)
|
||||||
|
{
|
||||||
|
if (!_entriesByService.TryGetValue((IService)service, out var entry)) return;
|
||||||
|
entry.LateTickIndex = index;
|
||||||
|
_entriesByService[(IService)service] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFixedTickIndex(IServiceFixedTickable service, int index)
|
||||||
|
{
|
||||||
|
if (!_entriesByService.TryGetValue((IService)service, out var entry)) return;
|
||||||
|
entry.FixedTickIndex = index;
|
||||||
|
_entriesByService[(IService)service] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateGizmoIndex(IServiceGizmoDrawable service, int index)
|
||||||
|
{
|
||||||
|
if (!_entriesByService.TryGetValue((IService)service, out var entry)) return;
|
||||||
|
entry.GizmoIndex = index;
|
||||||
|
_entriesByService[(IService)service] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlushPendingChanges()
|
||||||
|
{
|
||||||
|
if (_pendingChanges.Count == 0) return;
|
||||||
|
|
||||||
|
for (var i = 0; i < _pendingChanges.Count; i++)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < contracts.Count; i++)
|
var change = _pendingChanges[i];
|
||||||
_servicesByContract.Remove(contracts[i]);
|
if (change.IsAdd)
|
||||||
|
{
|
||||||
|
if (!_entriesByService.ContainsKey(change.Service))
|
||||||
|
{
|
||||||
|
((IServiceLifecycle)change.Service).Initialize(new ServiceContext(World, this));
|
||||||
|
AddEntry(change.Service, change.Contracts);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_entriesByService.TryGetValue(change.Service, out var entry))
|
||||||
|
{
|
||||||
|
RemoveEntry(change.Service, entry, true);
|
||||||
|
((IServiceLifecycle)change.Service).Destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_contractsByService.Remove(service);
|
_pendingChanges.Clear();
|
||||||
_registrationOrder.Remove(service);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used during full Dispose — skips the O(n) _registrationOrder.Remove since we clear the list afterwards.
|
private void SortTickablesIfDirty()
|
||||||
private void RemoveContractBindings(IService service)
|
|
||||||
{
|
|
||||||
if (_contractsByService.TryGetValue(service, out var contracts))
|
|
||||||
{
|
|
||||||
for (var i = 0; i < contracts.Count; i++)
|
|
||||||
_servicesByContract.Remove(contracts[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_contractsByService.Remove(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IServiceTickable[] GetTickSnapshot()
|
|
||||||
{
|
{
|
||||||
if (_tickablesDirty)
|
if (_tickablesDirty)
|
||||||
{
|
{
|
||||||
_tickables.Sort(CompareByOrder);
|
_tickables.Sort(CompareByOrder);
|
||||||
_tickableSnapshot = _tickables.Count > 0 ? _tickables.ToArray() : Array.Empty<IServiceTickable>();
|
RebuildTickIndices();
|
||||||
_tickablesDirty = false;
|
_tickablesDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _tickableSnapshot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceLateTickable[] GetLateTickSnapshot()
|
private void SortLateTickablesIfDirty()
|
||||||
{
|
{
|
||||||
if (_lateTickablesDirty)
|
if (_lateTickablesDirty)
|
||||||
{
|
{
|
||||||
_lateTickables.Sort(CompareByOrder);
|
_lateTickables.Sort(CompareByOrder);
|
||||||
_lateTickableSnapshot = _lateTickables.Count > 0 ? _lateTickables.ToArray() : Array.Empty<IServiceLateTickable>();
|
RebuildLateTickIndices();
|
||||||
_lateTickablesDirty = false;
|
_lateTickablesDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _lateTickableSnapshot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceFixedTickable[] GetFixedTickSnapshot()
|
private void SortFixedTickablesIfDirty()
|
||||||
{
|
{
|
||||||
if (_fixedTickablesDirty)
|
if (_fixedTickablesDirty)
|
||||||
{
|
{
|
||||||
_fixedTickables.Sort(CompareByOrder);
|
_fixedTickables.Sort(CompareByOrder);
|
||||||
_fixedTickableSnapshot = _fixedTickables.Count > 0 ? _fixedTickables.ToArray() : Array.Empty<IServiceFixedTickable>();
|
RebuildFixedTickIndices();
|
||||||
_fixedTickablesDirty = false;
|
_fixedTickablesDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _fixedTickableSnapshot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceGizmoDrawable[] GetGizmoSnapshot()
|
private void SortGizmoDrawablesIfDirty()
|
||||||
{
|
{
|
||||||
if (_gizmoDrawablesDirty)
|
if (_gizmoDrawablesDirty)
|
||||||
{
|
{
|
||||||
_gizmoDrawables.Sort(CompareByOrder);
|
_gizmoDrawables.Sort(CompareByOrder);
|
||||||
_gizmoSnapshot = _gizmoDrawables.Count > 0 ? _gizmoDrawables.ToArray() : Array.Empty<IServiceGizmoDrawable>();
|
RebuildGizmoIndices();
|
||||||
_gizmoDrawablesDirty = false;
|
_gizmoDrawablesDirty = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return _gizmoSnapshot;
|
private void RebuildTickIndices()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _tickables.Count; i++) UpdateTickIndex(_tickables[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildLateTickIndices()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _lateTickables.Count; i++) UpdateLateTickIndex(_lateTickables[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildFixedTickIndices()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _fixedTickables.Count; i++) UpdateFixedTickIndex(_fixedTickables[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildGizmoIndices()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _gizmoDrawables.Count; i++) UpdateGizmoIndex(_gizmoDrawables[i], i);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CompareByOrder<T>(T a, T b)
|
private static int CompareByOrder<T>(T a, T b)
|
||||||
@ -303,5 +486,47 @@ namespace AlicizaX
|
|||||||
var right = b is IServiceOrder ob ? ob.Order : 0;
|
var right = b is IServiceOrder ob ? ob.Order : 0;
|
||||||
return left.CompareTo(right);
|
return left.CompareTo(right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct ServiceEntry
|
||||||
|
{
|
||||||
|
public readonly ServiceContracts Contracts;
|
||||||
|
public int RegistrationIndex;
|
||||||
|
public int TickIndex;
|
||||||
|
public int LateTickIndex;
|
||||||
|
public int FixedTickIndex;
|
||||||
|
public int GizmoIndex;
|
||||||
|
public bool PendingRemove;
|
||||||
|
|
||||||
|
public ServiceEntry(ServiceContracts contracts, int registrationIndex)
|
||||||
|
{
|
||||||
|
Contracts = contracts;
|
||||||
|
RegistrationIndex = registrationIndex;
|
||||||
|
TickIndex = MissingIndex;
|
||||||
|
LateTickIndex = MissingIndex;
|
||||||
|
FixedTickIndex = MissingIndex;
|
||||||
|
GizmoIndex = MissingIndex;
|
||||||
|
PendingRemove = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly struct PendingChange
|
||||||
|
{
|
||||||
|
public readonly bool IsAdd;
|
||||||
|
public readonly IService Service;
|
||||||
|
public readonly ServiceContracts Contracts;
|
||||||
|
|
||||||
|
private PendingChange(bool isAdd, IService service, ServiceContracts contracts)
|
||||||
|
{
|
||||||
|
IsAdd = isAdd;
|
||||||
|
Service = service;
|
||||||
|
Contracts = contracts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PendingChange Add(IService service, ServiceContracts contracts)
|
||||||
|
=> new PendingChange(true, service, contracts);
|
||||||
|
|
||||||
|
public static PendingChange Remove(IService service)
|
||||||
|
=> new PendingChange(false, service, default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,31 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Cysharp.Text;
|
||||||
|
|
||||||
namespace AlicizaX
|
namespace AlicizaX
|
||||||
{
|
{
|
||||||
internal sealed class ServiceWorld : IDisposable
|
internal sealed class ServiceWorld : IDisposable
|
||||||
{
|
{
|
||||||
private readonly List<ServiceScope> _scopes = new List<ServiceScope>();
|
private const int ScopeSlotCount = 3;
|
||||||
private readonly Dictionary<Type, ServiceScope> _scopesByType = new Dictionary<Type, ServiceScope>();
|
|
||||||
|
|
||||||
private ServiceScope[] _scopeSnapshot = Array.Empty<ServiceScope>();
|
private readonly ServiceScope[] _scopesByKind = new ServiceScope[ScopeSlotCount];
|
||||||
|
private readonly ServiceScope[] _activeScopes = new ServiceScope[ScopeSlotCount];
|
||||||
|
private readonly Dictionary<RuntimeTypeHandle, ContractBindings> _servicesByContract = new Dictionary<RuntimeTypeHandle, ContractBindings>();
|
||||||
|
|
||||||
|
private int _activeScopeCount;
|
||||||
|
private int _nextScopeCreationIndex;
|
||||||
private bool _scopesDirty;
|
private bool _scopesDirty;
|
||||||
private ServiceScope _sceneScope;
|
|
||||||
private ServiceScope _gameplayScope;
|
|
||||||
|
|
||||||
internal ServiceWorld(int appScopeOrder = ServiceDomainOrder.App)
|
internal ServiceWorld(int appScopeOrder = ServiceDomainOrder.App)
|
||||||
{
|
{
|
||||||
App = CreateScopeInternal(typeof(AppScope), nameof(App), appScopeOrder);
|
App = CreateScopeInternal(ServiceScopeKind.App, nameof(App), appScopeOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ServiceScope App { get; }
|
internal ServiceScope App { get; }
|
||||||
|
|
||||||
internal bool HasScene => _sceneScope != null && !_sceneScope.IsDisposed;
|
internal bool HasScene => IsAlive(ServiceScopeKind.Scene);
|
||||||
|
|
||||||
internal bool HasGameplay => _gameplayScope != null && !_gameplayScope.IsDisposed;
|
internal bool HasGameplay => IsAlive(ServiceScopeKind.Gameplay);
|
||||||
|
|
||||||
internal ServiceScope Scene
|
internal ServiceScope Scene
|
||||||
{
|
{
|
||||||
@ -30,7 +33,7 @@ namespace AlicizaX
|
|||||||
{
|
{
|
||||||
if (!HasScene)
|
if (!HasScene)
|
||||||
throw new InvalidOperationException("Scene scope has not been created yet.");
|
throw new InvalidOperationException("Scene scope has not been created yet.");
|
||||||
return _sceneScope;
|
return _scopesByKind[(int)ServiceScopeKind.Scene];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,18 +43,18 @@ namespace AlicizaX
|
|||||||
{
|
{
|
||||||
if (!HasGameplay)
|
if (!HasGameplay)
|
||||||
throw new InvalidOperationException("Gameplay scope has not been created yet.");
|
throw new InvalidOperationException("Gameplay scope has not been created yet.");
|
||||||
return _gameplayScope;
|
return _scopesByKind[(int)ServiceScopeKind.Gameplay];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ServiceScope EnsureScene(int order = ServiceDomainOrder.Scene)
|
internal ServiceScope EnsureScene(int order = ServiceDomainOrder.Scene)
|
||||||
=> _sceneScope is { IsDisposed: false }
|
=> IsAlive(ServiceScopeKind.Scene)
|
||||||
? _sceneScope
|
? _scopesByKind[(int)ServiceScopeKind.Scene]
|
||||||
: _sceneScope = CreateScopeInternal(typeof(SceneScope), nameof(SceneScope), order);
|
: CreateScopeInternal(ServiceScopeKind.Scene, nameof(SceneScope), order);
|
||||||
|
|
||||||
internal bool TryGetScene(out ServiceScope scope)
|
internal bool TryGetScene(out ServiceScope scope)
|
||||||
{
|
{
|
||||||
scope = HasScene ? _sceneScope : null;
|
scope = IsAlive(ServiceScopeKind.Scene) ? _scopesByKind[(int)ServiceScopeKind.Scene] : null;
|
||||||
return scope != null;
|
return scope != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,35 +65,21 @@ namespace AlicizaX
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal bool DestroyScene()
|
internal bool DestroyScene()
|
||||||
{
|
=> DestroyScope(ServiceScopeKind.Scene);
|
||||||
if (!HasScene)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var scope = _sceneScope;
|
|
||||||
_sceneScope = null;
|
|
||||||
return DestroyScope(scope, typeof(SceneScope));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal ServiceScope EnsureGameplay(int order = ServiceDomainOrder.Gameplay)
|
internal ServiceScope EnsureGameplay(int order = ServiceDomainOrder.Gameplay)
|
||||||
=> _gameplayScope is { IsDisposed: false }
|
=> IsAlive(ServiceScopeKind.Gameplay)
|
||||||
? _gameplayScope
|
? _scopesByKind[(int)ServiceScopeKind.Gameplay]
|
||||||
: _gameplayScope = CreateScopeInternal(typeof(GameplayScope), nameof(GameplayScope), order);
|
: CreateScopeInternal(ServiceScopeKind.Gameplay, nameof(GameplayScope), order);
|
||||||
|
|
||||||
internal bool TryGetGameplay(out ServiceScope scope)
|
internal bool TryGetGameplay(out ServiceScope scope)
|
||||||
{
|
{
|
||||||
scope = HasGameplay ? _gameplayScope : null;
|
scope = IsAlive(ServiceScopeKind.Gameplay) ? _scopesByKind[(int)ServiceScopeKind.Gameplay] : null;
|
||||||
return scope != null;
|
return scope != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool DestroyGameplay()
|
internal bool DestroyGameplay()
|
||||||
{
|
=> DestroyScope(ServiceScopeKind.Gameplay);
|
||||||
if (!HasGameplay)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var scope = _gameplayScope;
|
|
||||||
_gameplayScope = null;
|
|
||||||
return DestroyScope(scope, typeof(GameplayScope));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool TryGet<T>(out T service) where T : class, IService
|
internal bool TryGet<T>(out T service) where T : class, IService
|
||||||
=> TryGet(null, out service);
|
=> TryGet(null, out service);
|
||||||
@ -100,12 +89,10 @@ namespace AlicizaX
|
|||||||
if (preferredScope != null && !preferredScope.IsDisposed && preferredScope.TryGet(out service))
|
if (preferredScope != null && !preferredScope.IsDisposed && preferredScope.TryGet(out service))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var snapshot = GetScopeSnapshot();
|
if (_servicesByContract.TryGetValue(typeof(T).TypeHandle, out var bindings) && bindings.TryGetBest(out var raw))
|
||||||
for (var i = snapshot.Length - 1; i >= 0; i--)
|
|
||||||
{
|
{
|
||||||
var scope = snapshot[i];
|
service = raw as T;
|
||||||
if (ReferenceEquals(scope, preferredScope)) continue;
|
return service != null;
|
||||||
if (scope.TryGet(out service)) return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
service = null;
|
service = null;
|
||||||
@ -117,82 +104,209 @@ namespace AlicizaX
|
|||||||
internal T Require<T>(ServiceScope preferredScope) where T : class, IService
|
internal T Require<T>(ServiceScope preferredScope) where T : class, IService
|
||||||
{
|
{
|
||||||
if (TryGet(preferredScope, out T service)) return service;
|
if (TryGet(preferredScope, out T service)) return service;
|
||||||
throw new InvalidOperationException($"Service {typeof(T).FullName} was not found in any active scope.");
|
throw new InvalidOperationException(ZString.Format("Service {0} was not found in any active scope.", typeof(T).FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Tick(float deltaTime)
|
internal void Tick(float deltaTime)
|
||||||
{
|
{
|
||||||
var snapshot = GetScopeSnapshot();
|
SortScopesIfDirty();
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].Tick(deltaTime);
|
for (var i = 0; i < _activeScopeCount; i++) _activeScopes[i].Tick(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void LateTick(float deltaTime)
|
internal void LateTick(float deltaTime)
|
||||||
{
|
{
|
||||||
var snapshot = GetScopeSnapshot();
|
SortScopesIfDirty();
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].LateTick(deltaTime);
|
for (var i = 0; i < _activeScopeCount; i++) _activeScopes[i].LateTick(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void FixedTick(float fixedDeltaTime)
|
internal void FixedTick(float fixedDeltaTime)
|
||||||
{
|
{
|
||||||
var snapshot = GetScopeSnapshot();
|
SortScopesIfDirty();
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].FixedTick(fixedDeltaTime);
|
for (var i = 0; i < _activeScopeCount; i++) _activeScopes[i].FixedTick(fixedDeltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DrawGizmos()
|
internal void DrawGizmos()
|
||||||
{
|
{
|
||||||
var snapshot = GetScopeSnapshot();
|
SortScopesIfDirty();
|
||||||
for (var i = 0; i < snapshot.Length; i++) snapshot[i].DrawGizmos();
|
for (var i = 0; i < _activeScopeCount; i++) _activeScopes[i].DrawGizmos();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
var snapshot = _scopes.ToArray();
|
for (var i = _activeScopeCount - 1; i >= 0; i--)
|
||||||
for (var i = snapshot.Length - 1; i >= 0; i--)
|
_activeScopes[i].Dispose();
|
||||||
snapshot[i].Dispose();
|
|
||||||
|
|
||||||
_sceneScope = null;
|
for (var i = 0; i < ScopeSlotCount; i++)
|
||||||
_gameplayScope = null;
|
_scopesByKind[i] = null;
|
||||||
_scopes.Clear();
|
|
||||||
_scopesByType.Clear();
|
_activeScopeCount = 0;
|
||||||
_scopeSnapshot = Array.Empty<ServiceScope>();
|
_servicesByContract.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DestroyScope(ServiceScope scope, Type scopeType)
|
internal void AddContract(ServiceScope scope, Type contract, IService service)
|
||||||
{
|
{
|
||||||
if (scope == null)
|
var contractHandle = contract.TypeHandle;
|
||||||
return false;
|
if (!_servicesByContract.TryGetValue(contractHandle, out var bindings))
|
||||||
|
{
|
||||||
|
bindings = default;
|
||||||
|
_servicesByContract.Add(contractHandle, bindings);
|
||||||
|
}
|
||||||
|
|
||||||
_scopesByType.Remove(scopeType);
|
bindings.Set(scope.Kind, scope, service);
|
||||||
_scopes.Remove(scope);
|
_servicesByContract[contractHandle] = bindings;
|
||||||
_scopesDirty = true;
|
}
|
||||||
|
|
||||||
|
internal void RemoveContract(ServiceScope scope, Type contract, IService service)
|
||||||
|
{
|
||||||
|
var contractHandle = contract.TypeHandle;
|
||||||
|
if (!_servicesByContract.TryGetValue(contractHandle, out var bindings)) return;
|
||||||
|
|
||||||
|
bindings.Clear(scope.Kind, service);
|
||||||
|
if (bindings.IsEmpty)
|
||||||
|
_servicesByContract.Remove(contractHandle);
|
||||||
|
else
|
||||||
|
_servicesByContract[contractHandle] = bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsAlive(ServiceScopeKind kind)
|
||||||
|
{
|
||||||
|
var scope = _scopesByKind[(int)kind];
|
||||||
|
return scope != null && !scope.IsDisposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DestroyScope(ServiceScopeKind kind)
|
||||||
|
{
|
||||||
|
if (!IsAlive(kind)) return false;
|
||||||
|
|
||||||
|
var scope = _scopesByKind[(int)kind];
|
||||||
|
_scopesByKind[(int)kind] = null;
|
||||||
|
RemoveActiveScope(scope);
|
||||||
scope.Dispose();
|
scope.Dispose();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServiceScope CreateScopeInternal(Type scopeType, string scopeName, int order)
|
private ServiceScope CreateScopeInternal(ServiceScopeKind kind, string scopeName, int order)
|
||||||
{
|
{
|
||||||
if (_scopesByType.ContainsKey(scopeType))
|
if (IsAlive(kind))
|
||||||
throw new InvalidOperationException($"Scope {scopeType.Name} already exists.");
|
throw new InvalidOperationException(ZString.Format("Scope {0} already exists.", scopeName));
|
||||||
|
|
||||||
var scope = new ServiceScope(this, scopeName, order);
|
var scope = new ServiceScope(this, kind, scopeName, order, _nextScopeCreationIndex++);
|
||||||
_scopes.Add(scope);
|
_scopesByKind[(int)kind] = scope;
|
||||||
_scopesByType.Add(scopeType, scope);
|
_activeScopes[_activeScopeCount++] = scope;
|
||||||
_scopes.Sort(CompareScopeOrder);
|
|
||||||
_scopesDirty = true;
|
_scopesDirty = true;
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServiceScope[] GetScopeSnapshot()
|
private void RemoveActiveScope(ServiceScope scope)
|
||||||
{
|
{
|
||||||
if (_scopesDirty)
|
for (var i = 0; i < _activeScopeCount; i++)
|
||||||
{
|
{
|
||||||
_scopeSnapshot = _scopes.Count > 0 ? _scopes.ToArray() : Array.Empty<ServiceScope>();
|
if (!ReferenceEquals(_activeScopes[i], scope)) continue;
|
||||||
_scopesDirty = false;
|
|
||||||
|
_activeScopeCount--;
|
||||||
|
_activeScopes[i] = _activeScopes[_activeScopeCount];
|
||||||
|
_activeScopes[_activeScopeCount] = null;
|
||||||
|
_scopesDirty = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return _scopeSnapshot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CompareScopeOrder(ServiceScope left, ServiceScope right)
|
private void SortScopesIfDirty()
|
||||||
=> left.Order.CompareTo(right.Order);
|
{
|
||||||
|
if (!_scopesDirty) return;
|
||||||
|
|
||||||
|
for (var i = 1; i < _activeScopeCount; i++)
|
||||||
|
{
|
||||||
|
var scope = _activeScopes[i];
|
||||||
|
var index = i - 1;
|
||||||
|
while (index >= 0 && HasHigherPriority(_activeScopes[index], scope))
|
||||||
|
{
|
||||||
|
_activeScopes[index + 1] = _activeScopes[index];
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeScopes[index + 1] = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scopesDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasHigherPriority(ServiceScope left, ServiceScope right)
|
||||||
|
=> left.Order > right.Order || left.Order == right.Order && left.CreationIndex > right.CreationIndex;
|
||||||
|
|
||||||
|
private struct ContractBindings
|
||||||
|
{
|
||||||
|
private ServiceBinding _app;
|
||||||
|
private ServiceBinding _scene;
|
||||||
|
private ServiceBinding _gameplay;
|
||||||
|
|
||||||
|
public bool IsEmpty => !_app.HasValue && !_scene.HasValue && !_gameplay.HasValue;
|
||||||
|
|
||||||
|
public void Set(ServiceScopeKind kind, ServiceScope scope, IService service)
|
||||||
|
{
|
||||||
|
var binding = new ServiceBinding(scope, service);
|
||||||
|
if (kind == ServiceScopeKind.App)
|
||||||
|
_app = binding;
|
||||||
|
else if (kind == ServiceScopeKind.Scene)
|
||||||
|
_scene = binding;
|
||||||
|
else
|
||||||
|
_gameplay = binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear(ServiceScopeKind kind, IService service)
|
||||||
|
{
|
||||||
|
if (kind == ServiceScopeKind.App)
|
||||||
|
{
|
||||||
|
if (_app.HasValue && ReferenceEquals(_app.Service, service)) _app = default;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind == ServiceScopeKind.Scene)
|
||||||
|
{
|
||||||
|
if (_scene.HasValue && ReferenceEquals(_scene.Service, service)) _scene = default;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gameplay.HasValue && ReferenceEquals(_gameplay.Service, service)) _gameplay = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetBest(out IService service)
|
||||||
|
{
|
||||||
|
if (_gameplay.HasValue)
|
||||||
|
{
|
||||||
|
service = _gameplay.Service;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_scene.HasValue)
|
||||||
|
{
|
||||||
|
service = _scene.Service;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_app.HasValue)
|
||||||
|
{
|
||||||
|
service = _app.Service;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
service = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ServiceBinding
|
||||||
|
{
|
||||||
|
public ServiceScope Scope;
|
||||||
|
public IService Service;
|
||||||
|
public bool HasValue;
|
||||||
|
|
||||||
|
public ServiceBinding(ServiceScope scope, IService service)
|
||||||
|
{
|
||||||
|
Scope = scope;
|
||||||
|
Service = service;
|
||||||
|
HasValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
using Cysharp.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace AlicizaX
|
namespace AlicizaX
|
||||||
{
|
{
|
||||||
[DefaultExecutionOrder(-32000)]
|
[DefaultExecutionOrder(-32000)]
|
||||||
[DisallowMultipleComponent]
|
[DisallowMultipleComponent]
|
||||||
public class AppServiceRoot : MonoBehaviour
|
public abstract class AppServiceRoot : MonoBehaviour
|
||||||
{
|
{
|
||||||
private static AppServiceRoot s_activeRoot;
|
private static AppServiceRoot s_activeRoot;
|
||||||
|
|
||||||
@ -55,14 +54,13 @@ namespace AlicizaX
|
|||||||
if (AppServices.HasWorld) AppServices.RequireWorld().DrawGizmos();
|
if (AppServices.HasWorld) AppServices.RequireWorld().DrawGizmos();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual async void OnDestroy()
|
protected virtual void OnDestroy()
|
||||||
{
|
{
|
||||||
if (s_activeRoot == this)
|
if (s_activeRoot == this)
|
||||||
s_activeRoot = null;
|
s_activeRoot = null;
|
||||||
|
|
||||||
if (_ownsWorld && AppServices.HasWorld)
|
if (_ownsWorld && AppServices.HasWorld)
|
||||||
{
|
{
|
||||||
await UniTask.Yield();
|
|
||||||
AppServices.Shutdown();
|
AppServices.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
using Cysharp.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace AlicizaX
|
namespace AlicizaX
|
||||||
@ -11,7 +12,7 @@ namespace AlicizaX
|
|||||||
void IServiceLifecycle.Initialize(ServiceContext context)
|
void IServiceLifecycle.Initialize(ServiceContext context)
|
||||||
{
|
{
|
||||||
if (IsInitialized)
|
if (IsInitialized)
|
||||||
throw new System.InvalidOperationException($"{GetType().FullName} is already initialized.");
|
throw new System.InvalidOperationException(ZString.Format("{0} is already initialized.", GetType().FullName));
|
||||||
|
|
||||||
Context = context;
|
Context = context;
|
||||||
IsInitialized = true;
|
IsInitialized = true;
|
||||||
@ -55,7 +56,7 @@ namespace AlicizaX
|
|||||||
if (_dontDestroyOnLoad)
|
if (_dontDestroyOnLoad)
|
||||||
DontDestroyOnLoad(gameObject);
|
DontDestroyOnLoad(gameObject);
|
||||||
|
|
||||||
scope.Register(this);
|
scope.RegisterSelf(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
@ -69,36 +70,41 @@ namespace AlicizaX
|
|||||||
|
|
||||||
private static ServiceScope ResolveOrCreateScope()
|
private static ServiceScope ResolveOrCreateScope()
|
||||||
{
|
{
|
||||||
if (typeof(TScope) == typeof(AppScope))
|
var kind = ScopeKindCache<TScope>.Kind;
|
||||||
return AppServices.RequireWorld().App;
|
if (kind == ServiceScopeKind.App) return AppServices.RequireWorld().App;
|
||||||
|
if (kind == ServiceScopeKind.Scene) return AppServices.EnsureScene();
|
||||||
if (typeof(TScope) == typeof(SceneScope))
|
return AppServices.EnsureGameplay();
|
||||||
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)
|
private static bool TryResolveScope(out ServiceScope scope)
|
||||||
{
|
{
|
||||||
if (typeof(TScope) == typeof(AppScope))
|
var kind = ScopeKindCache<TScope>.Kind;
|
||||||
|
if (kind == ServiceScopeKind.App)
|
||||||
{
|
{
|
||||||
scope = AppServices.RequireWorld().App;
|
scope = AppServices.RequireWorld().App;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof(TScope) == typeof(SceneScope))
|
if (kind == ServiceScopeKind.Scene)
|
||||||
return AppServices.TryGetScene(out scope);
|
return AppServices.TryGetScene(out scope);
|
||||||
|
|
||||||
if (typeof(TScope) == typeof(GameplayScope))
|
return AppServices.TryGetGameplay(out scope);
|
||||||
return AppServices.TryGetGameplay(out scope);
|
|
||||||
|
|
||||||
scope = null;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnAwake() { }
|
protected virtual void OnAwake() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static class ScopeKindCache<TScope>
|
||||||
|
where TScope : IScope
|
||||||
|
{
|
||||||
|
public static readonly ServiceScopeKind Kind = Resolve();
|
||||||
|
|
||||||
|
private static ServiceScopeKind Resolve()
|
||||||
|
{
|
||||||
|
if (typeof(TScope) == typeof(AppScope)) return ServiceScopeKind.App;
|
||||||
|
if (typeof(TScope) == typeof(SceneScope)) return ServiceScopeKind.Scene;
|
||||||
|
if (typeof(TScope) == typeof(GameplayScope)) return ServiceScopeKind.Gameplay;
|
||||||
|
throw new System.InvalidOperationException(ZString.Format("Unsupported service scope: {0}.", typeof(TScope).FullName));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -286,7 +286,7 @@ namespace AlicizaX
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_loopService = AppServices.RegisterApp(new UtilityLoopService());
|
_loopService = AppServices.RegisterAppSelf(new UtilityLoopService());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UtilityLoopService EnsureLoopService()
|
private static UtilityLoopService EnsureLoopService()
|
||||||
|
|||||||
@ -16,7 +16,7 @@ namespace AlicizaX.Audio.Runtime
|
|||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_audioService = AppServices.RegisterApp(new AudioService());
|
_audioService = AppServices.RegisterApp<IAudioService>(new AudioService());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
|
|||||||
@ -8,83 +8,89 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
{
|
{
|
||||||
private sealed class TimerInformationWindow : ScrollableDebuggerWindowBase
|
private sealed class TimerInformationWindow : ScrollableDebuggerWindowBase
|
||||||
{
|
{
|
||||||
private const int MAX_DISPLAY_COUNT = 50;
|
private const int MAX_DISPLAY_COUNT = 64;
|
||||||
private const float REFRESH_INTERVAL = 0.25f;
|
private const float REFRESH_INTERVAL = 0.25f;
|
||||||
private const string OVERFLOW_NOTE = "Showing first 50 active timers.";
|
private const string OVERFLOW_NOTE = "Active timer count exceeds visible sample.";
|
||||||
private const string EMPTY_NOTE = "No active timers.";
|
private const string EMPTY_NOTE = "No active timers.";
|
||||||
private const string SAMPLE_NOTE = "Zero-allocation runtime sample view.";
|
private const string SAMPLE_NOTE = "Runtime view uses fixed buffers and refresh-only style updates.";
|
||||||
|
|
||||||
|
private struct UsageView
|
||||||
|
{
|
||||||
|
public VisualElement Fill;
|
||||||
|
}
|
||||||
|
|
||||||
private struct RowView
|
private struct RowView
|
||||||
{
|
{
|
||||||
public VisualElement Root;
|
public VisualElement Root;
|
||||||
|
public VisualElement Fill;
|
||||||
public VisualElement LoopIndicator;
|
public VisualElement LoopIndicator;
|
||||||
public VisualElement ScaleIndicator;
|
public VisualElement ScaleIndicator;
|
||||||
public VisualElement StateIndicator;
|
public VisualElement StateIndicator;
|
||||||
public VisualElement Fill;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ITimerService _mTimerDebug;
|
private ITimerService _timerService;
|
||||||
private readonly TimerDebugInfo[] m_TimerInfos = new TimerDebugInfo[MAX_DISPLAY_COUNT];
|
private ITimerDebugService _timerDebugService;
|
||||||
private readonly RowView[] m_TimerRows = new RowView[MAX_DISPLAY_COUNT];
|
private readonly TimerDebugInfo[] _timerInfos = new TimerDebugInfo[MAX_DISPLAY_COUNT];
|
||||||
private VisualElement m_ActiveUsageFill;
|
private readonly RowView[] _timerRows = new RowView[MAX_DISPLAY_COUNT];
|
||||||
private VisualElement m_PeakUsageFill;
|
private UsageView _activeUsage;
|
||||||
private VisualElement m_FreeUsageFill;
|
private UsageView _peakUsage;
|
||||||
private VisualElement m_OverflowNote;
|
private UsageView _freeUsage;
|
||||||
private VisualElement m_EmptyNote;
|
private VisualElement _overflowNote;
|
||||||
private float m_RefreshCountdown;
|
private VisualElement _emptyNote;
|
||||||
|
private float _refreshCountdown;
|
||||||
|
private int _visibleRowCount;
|
||||||
|
|
||||||
public override void Initialize(params object[] args)
|
public override void Initialize(params object[] args)
|
||||||
{
|
{
|
||||||
_mTimerDebug = AppServices.Require<ITimerService>();
|
if (AppServices.TryGet(out _timerService))
|
||||||
|
{
|
||||||
|
_timerDebugService = _timerService as ITimerDebugService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnEnter()
|
public override void OnEnter()
|
||||||
{
|
{
|
||||||
m_RefreshCountdown = 0f;
|
_refreshCountdown = 0f;
|
||||||
RefreshContent();
|
RefreshContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||||
{
|
{
|
||||||
m_RefreshCountdown -= realElapseSeconds;
|
_refreshCountdown -= realElapseSeconds;
|
||||||
if (m_RefreshCountdown > 0f)
|
if (_refreshCountdown > 0f)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_RefreshCountdown = REFRESH_INTERVAL;
|
_refreshCountdown = REFRESH_INTERVAL;
|
||||||
RefreshContent();
|
RefreshContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void BuildWindow(VisualElement root)
|
protected override void BuildWindow(VisualElement root)
|
||||||
{
|
{
|
||||||
if (_mTimerDebug == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.Add(CreateActionButton("Refresh", RefreshContent, DebuggerTheme.ButtonSurfaceActive, DebuggerTheme.PrimaryText));
|
root.Add(CreateActionButton("Refresh", RefreshContent, DebuggerTheme.ButtonSurfaceActive, DebuggerTheme.PrimaryText));
|
||||||
|
|
||||||
VisualElement overview = CreateSection("Timer Pool Overview", out VisualElement overviewCard);
|
VisualElement overview = CreateSection("Timer Pool", out VisualElement overviewCard);
|
||||||
overviewCard.Add(CreateUsageRow("Active Usage", DebuggerTheme.Accent, out m_ActiveUsageFill));
|
overviewCard.Add(CreateUsageRow("Active", DebuggerTheme.Accent, out _activeUsage));
|
||||||
overviewCard.Add(CreateUsageRow("Peak Usage", DebuggerTheme.Warning, out m_PeakUsageFill));
|
overviewCard.Add(CreateUsageRow("Peak", DebuggerTheme.Warning, out _peakUsage));
|
||||||
overviewCard.Add(CreateUsageRow("Free Capacity", DebuggerTheme.Positive, out m_FreeUsageFill));
|
overviewCard.Add(CreateUsageRow("Free", DebuggerTheme.Positive, out _freeUsage));
|
||||||
root.Add(overview);
|
root.Add(overview);
|
||||||
|
|
||||||
VisualElement sample = CreateSection("Timer Sample", out VisualElement sampleCard);
|
VisualElement sample = CreateSection("Timer Sample", out VisualElement sampleCard);
|
||||||
sampleCard.Add(CreateNoteLabel(SAMPLE_NOTE, DebuggerTheme.SecondaryText));
|
sampleCard.Add(CreateNoteLabel(SAMPLE_NOTE, DebuggerTheme.SecondaryText));
|
||||||
m_OverflowNote = CreateNoteLabel(OVERFLOW_NOTE, new Color(1f, 0.5f, 0f));
|
_overflowNote = CreateNoteLabel(OVERFLOW_NOTE, DebuggerTheme.Warning);
|
||||||
m_OverflowNote.style.display = DisplayStyle.None;
|
_overflowNote.style.display = DisplayStyle.None;
|
||||||
sampleCard.Add(m_OverflowNote);
|
sampleCard.Add(_overflowNote);
|
||||||
|
|
||||||
m_EmptyNote = CreateNoteLabel(EMPTY_NOTE, DebuggerTheme.SecondaryText);
|
_emptyNote = CreateNoteLabel(EMPTY_NOTE, DebuggerTheme.SecondaryText);
|
||||||
m_EmptyNote.style.display = DisplayStyle.None;
|
_emptyNote.style.display = DisplayStyle.None;
|
||||||
sampleCard.Add(m_EmptyNote);
|
sampleCard.Add(_emptyNote);
|
||||||
|
|
||||||
|
_visibleRowCount = 0;
|
||||||
for (int i = 0; i < MAX_DISPLAY_COUNT; i++)
|
for (int i = 0; i < MAX_DISPLAY_COUNT; i++)
|
||||||
{
|
{
|
||||||
m_TimerRows[i] = CreateTimerRow(sampleCard);
|
_timerRows[i] = CreateTimerRow(sampleCard);
|
||||||
m_TimerRows[i].Root.style.display = DisplayStyle.None;
|
_timerRows[i].Root.style.display = DisplayStyle.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.Add(sample);
|
root.Add(sample);
|
||||||
@ -93,50 +99,76 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
|
|
||||||
private void RefreshContent()
|
private void RefreshContent()
|
||||||
{
|
{
|
||||||
if (_mTimerDebug == null || m_ActiveUsageFill == null)
|
if (_emptyNote == null || _overflowNote == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_mTimerDebug.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
|
if (_timerService == null)
|
||||||
|
{
|
||||||
|
if (AppServices.TryGet(out _timerService))
|
||||||
|
{
|
||||||
|
_timerDebugService = _timerService as ITimerDebugService;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_timerDebugService == null)
|
||||||
|
{
|
||||||
|
SetVisibleRows(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_timerDebugService.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
|
||||||
float capacity = poolCapacity > 0 ? poolCapacity : 1f;
|
float capacity = poolCapacity > 0 ? poolCapacity : 1f;
|
||||||
SetFillRatio(m_ActiveUsageFill, activeCount / capacity);
|
SetFillRatio(_activeUsage.Fill, activeCount / capacity);
|
||||||
SetFillRatio(m_PeakUsageFill, peakActiveCount / capacity);
|
SetFillRatio(_peakUsage.Fill, peakActiveCount / capacity);
|
||||||
SetFillRatio(m_FreeUsageFill, freeCount / capacity);
|
SetFillRatio(_freeUsage.Fill, freeCount / capacity);
|
||||||
|
|
||||||
if (activeCount <= 0)
|
if (activeCount <= 0)
|
||||||
{
|
{
|
||||||
m_EmptyNote.style.display = DisplayStyle.Flex;
|
_emptyNote.style.display = DisplayStyle.Flex;
|
||||||
m_OverflowNote.style.display = DisplayStyle.None;
|
_overflowNote.style.display = DisplayStyle.None;
|
||||||
SetTimerRowsVisible(0);
|
SetVisibleRows(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_EmptyNote.style.display = DisplayStyle.None;
|
_emptyNote.style.display = DisplayStyle.None;
|
||||||
|
_overflowNote.style.display = activeCount > MAX_DISPLAY_COUNT ? DisplayStyle.Flex : DisplayStyle.None;
|
||||||
int timerCount = _mTimerDebug.GetAllTimers(m_TimerInfos);
|
|
||||||
int displayCount = timerCount > MAX_DISPLAY_COUNT ? MAX_DISPLAY_COUNT : timerCount;
|
|
||||||
m_OverflowNote.style.display = activeCount > MAX_DISPLAY_COUNT ? DisplayStyle.Flex : DisplayStyle.None;
|
|
||||||
|
|
||||||
|
int timerCount = _timerDebugService.GetAllTimers(_timerInfos);
|
||||||
|
int displayCount = timerCount < MAX_DISPLAY_COUNT ? timerCount : MAX_DISPLAY_COUNT;
|
||||||
for (int i = 0; i < displayCount; i++)
|
for (int i = 0; i < displayCount; i++)
|
||||||
{
|
{
|
||||||
UpdateTimerRow(ref m_TimerRows[i], ref m_TimerInfos[i]);
|
UpdateTimerRow(ref _timerRows[i], ref _timerInfos[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetTimerRowsVisible(displayCount);
|
SetVisibleRows(displayCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetTimerRowsVisible(int visibleCount)
|
private void SetVisibleRows(int visibleCount)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < MAX_DISPLAY_COUNT; i++)
|
if (_visibleRowCount == visibleCount)
|
||||||
{
|
{
|
||||||
m_TimerRows[i].Root.style.display = i < visibleCount ? DisplayStyle.Flex : DisplayStyle.None;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int minCount = _visibleRowCount < visibleCount ? _visibleRowCount : visibleCount;
|
||||||
|
for (int i = minCount; i < visibleCount; i++)
|
||||||
|
{
|
||||||
|
_timerRows[i].Root.style.display = DisplayStyle.Flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = visibleCount; i < _visibleRowCount; i++)
|
||||||
|
{
|
||||||
|
_timerRows[i].Root.style.display = DisplayStyle.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
_visibleRowCount = visibleCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VisualElement CreateUsageRow(string title, Color fillColor, out VisualElement fill)
|
private static VisualElement CreateUsageRow(string title, Color fillColor, out UsageView usageView)
|
||||||
{
|
{
|
||||||
float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f;
|
float scale = GetScale();
|
||||||
VisualElement row = new VisualElement();
|
VisualElement row = new VisualElement();
|
||||||
row.style.flexDirection = FlexDirection.Column;
|
row.style.flexDirection = FlexDirection.Column;
|
||||||
row.style.marginBottom = 8f * scale;
|
row.style.marginBottom = 8f * scale;
|
||||||
@ -148,80 +180,72 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
titleLabel.style.marginBottom = 4f * scale;
|
titleLabel.style.marginBottom = 4f * scale;
|
||||||
row.Add(titleLabel);
|
row.Add(titleLabel);
|
||||||
|
|
||||||
VisualElement track = new VisualElement();
|
VisualElement track = CreateTrack(14f * scale);
|
||||||
track.style.height = 14f * scale;
|
VisualElement fill = new VisualElement();
|
||||||
track.style.backgroundColor = DebuggerTheme.PanelSurfaceStrong;
|
|
||||||
track.style.borderTopLeftRadius = 4f * scale;
|
|
||||||
track.style.borderTopRightRadius = 4f * scale;
|
|
||||||
track.style.borderBottomLeftRadius = 4f * scale;
|
|
||||||
track.style.borderBottomRightRadius = 4f * scale;
|
|
||||||
track.style.overflow = Overflow.Hidden;
|
|
||||||
|
|
||||||
fill = new VisualElement();
|
|
||||||
fill.style.height = Length.Percent(100f);
|
fill.style.height = Length.Percent(100f);
|
||||||
fill.style.width = Length.Percent(0f);
|
fill.style.width = Length.Percent(0f);
|
||||||
fill.style.backgroundColor = fillColor;
|
fill.style.backgroundColor = fillColor;
|
||||||
track.Add(fill);
|
track.Add(fill);
|
||||||
row.Add(track);
|
row.Add(track);
|
||||||
|
|
||||||
|
usageView.Fill = fill;
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VisualElement CreateNoteLabel(string text, Color color)
|
|
||||||
{
|
|
||||||
float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f;
|
|
||||||
Label label = new Label(text);
|
|
||||||
label.style.color = color;
|
|
||||||
label.style.fontSize = 15f * scale;
|
|
||||||
label.style.marginBottom = 6f * scale;
|
|
||||||
label.style.whiteSpace = WhiteSpace.Normal;
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RowView CreateTimerRow(VisualElement parent)
|
private static RowView CreateTimerRow(VisualElement parent)
|
||||||
{
|
{
|
||||||
float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f;
|
float scale = GetScale();
|
||||||
VisualElement row = new VisualElement();
|
VisualElement row = new VisualElement();
|
||||||
row.style.flexDirection = FlexDirection.Row;
|
row.style.flexDirection = FlexDirection.Row;
|
||||||
row.style.alignItems = Align.Center;
|
row.style.alignItems = Align.Center;
|
||||||
row.style.height = 20f * scale;
|
row.style.height = 20f * scale;
|
||||||
row.style.marginBottom = 4f * scale;
|
row.style.marginBottom = 4f * scale;
|
||||||
|
|
||||||
VisualElement loopIndicator = CreateIndicator(6f * scale, DebuggerTheme.Accent);
|
|
||||||
VisualElement scaleIndicator = CreateIndicator(6f * scale, DebuggerTheme.Warning);
|
|
||||||
VisualElement stateIndicator = CreateIndicator(6f * scale, DebuggerTheme.Positive);
|
|
||||||
|
|
||||||
row.Add(loopIndicator);
|
|
||||||
row.Add(scaleIndicator);
|
|
||||||
row.Add(stateIndicator);
|
|
||||||
|
|
||||||
VisualElement track = new VisualElement();
|
|
||||||
track.style.flexGrow = 1f;
|
|
||||||
track.style.height = 14f * scale;
|
|
||||||
track.style.backgroundColor = DebuggerTheme.PanelSurfaceStrong;
|
|
||||||
track.style.borderTopLeftRadius = 4f * scale;
|
|
||||||
track.style.borderTopRightRadius = 4f * scale;
|
|
||||||
track.style.borderBottomLeftRadius = 4f * scale;
|
|
||||||
track.style.borderBottomRightRadius = 4f * scale;
|
|
||||||
track.style.overflow = Overflow.Hidden;
|
|
||||||
|
|
||||||
VisualElement fill = new VisualElement();
|
|
||||||
fill.style.height = Length.Percent(100f);
|
|
||||||
fill.style.width = Length.Percent(0f);
|
|
||||||
fill.style.backgroundColor = DebuggerTheme.Positive;
|
|
||||||
track.Add(fill);
|
|
||||||
row.Add(track);
|
|
||||||
parent.Add(row);
|
|
||||||
|
|
||||||
RowView view;
|
RowView view;
|
||||||
view.Root = row;
|
view.Root = row;
|
||||||
view.LoopIndicator = loopIndicator;
|
view.LoopIndicator = CreateIndicator(6f * scale, DebuggerTheme.Accent);
|
||||||
view.ScaleIndicator = scaleIndicator;
|
view.ScaleIndicator = CreateIndicator(6f * scale, DebuggerTheme.Warning);
|
||||||
view.StateIndicator = stateIndicator;
|
view.StateIndicator = CreateIndicator(6f * scale, DebuggerTheme.Positive);
|
||||||
view.Fill = fill;
|
row.Add(view.LoopIndicator);
|
||||||
|
row.Add(view.ScaleIndicator);
|
||||||
|
row.Add(view.StateIndicator);
|
||||||
|
|
||||||
|
VisualElement track = CreateTrack(14f * scale);
|
||||||
|
view.Fill = new VisualElement();
|
||||||
|
view.Fill.style.height = Length.Percent(100f);
|
||||||
|
view.Fill.style.width = Length.Percent(0f);
|
||||||
|
view.Fill.style.backgroundColor = DebuggerTheme.Positive;
|
||||||
|
track.Add(view.Fill);
|
||||||
|
row.Add(track);
|
||||||
|
parent.Add(row);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static VisualElement CreateTrack(float height)
|
||||||
|
{
|
||||||
|
VisualElement track = new VisualElement();
|
||||||
|
track.style.flexGrow = 1f;
|
||||||
|
track.style.height = height;
|
||||||
|
track.style.backgroundColor = DebuggerTheme.PanelSurfaceStrong;
|
||||||
|
track.style.borderTopLeftRadius = 4f;
|
||||||
|
track.style.borderTopRightRadius = 4f;
|
||||||
|
track.style.borderBottomLeftRadius = 4f;
|
||||||
|
track.style.borderBottomRightRadius = 4f;
|
||||||
|
track.style.overflow = Overflow.Hidden;
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label CreateNoteLabel(string text, Color color)
|
||||||
|
{
|
||||||
|
float scale = GetScale();
|
||||||
|
Label label = new Label(text);
|
||||||
|
label.style.color = color;
|
||||||
|
label.style.fontSize = 15f * scale;
|
||||||
|
label.style.marginBottom = 6f * scale;
|
||||||
|
label.style.whiteSpace = WhiteSpace.Normal;
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
private static VisualElement CreateIndicator(float size, Color color)
|
private static VisualElement CreateIndicator(float size, Color color)
|
||||||
{
|
{
|
||||||
VisualElement indicator = new VisualElement();
|
VisualElement indicator = new VisualElement();
|
||||||
@ -239,31 +263,24 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
bool isRunning = (flags & TimerDebugFlags.Running) != 0;
|
bool isRunning = (flags & TimerDebugFlags.Running) != 0;
|
||||||
bool isLoop = (flags & TimerDebugFlags.Loop) != 0;
|
bool isLoop = (flags & TimerDebugFlags.Loop) != 0;
|
||||||
bool isUnscaled = (flags & TimerDebugFlags.Unscaled) != 0;
|
bool isUnscaled = (flags & TimerDebugFlags.Unscaled) != 0;
|
||||||
float duration = info.Duration;
|
float ratio = info.Duration > 0f ? info.LeftTime / info.Duration : 0f;
|
||||||
float ratio = duration > 0f ? info.LeftTime / duration : 0f;
|
|
||||||
if (ratio < 0f)
|
|
||||||
{
|
|
||||||
ratio = 0f;
|
|
||||||
}
|
|
||||||
else if (ratio > 1f)
|
|
||||||
{
|
|
||||||
ratio = 1f;
|
|
||||||
}
|
|
||||||
|
|
||||||
row.Root.style.display = DisplayStyle.Flex;
|
|
||||||
row.LoopIndicator.style.opacity = isLoop ? 1f : 0.2f;
|
row.LoopIndicator.style.opacity = isLoop ? 1f : 0.2f;
|
||||||
row.ScaleIndicator.style.opacity = isUnscaled ? 1f : 0.35f;
|
row.ScaleIndicator.style.opacity = isUnscaled ? 1f : 0.35f;
|
||||||
row.ScaleIndicator.style.backgroundColor = isUnscaled ? DebuggerTheme.Warning : DebuggerTheme.Accent;
|
row.ScaleIndicator.style.backgroundColor = isUnscaled ? DebuggerTheme.Warning : DebuggerTheme.Accent;
|
||||||
row.StateIndicator.style.opacity = isRunning ? 1f : 0.45f;
|
row.StateIndicator.style.opacity = isRunning ? 1f : 0.45f;
|
||||||
row.StateIndicator.style.backgroundColor = isRunning ? DebuggerTheme.Positive : DebuggerTheme.Warning;
|
row.StateIndicator.style.backgroundColor = isRunning ? DebuggerTheme.Positive : DebuggerTheme.Warning;
|
||||||
row.Fill.style.width = Length.Percent(ratio * 100f);
|
row.Fill.style.backgroundColor = isRunning ? ratio <= 0.2f ? DebuggerTheme.Warning : DebuggerTheme.Positive : DebuggerTheme.SecondaryText;
|
||||||
row.Fill.style.backgroundColor = isRunning
|
SetFillRatio(row.Fill, ratio);
|
||||||
? ratio <= 0.2f ? DebuggerTheme.Warning : DebuggerTheme.Positive
|
|
||||||
: DebuggerTheme.SecondaryText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetFillRatio(VisualElement fill, float ratio)
|
private static void SetFillRatio(VisualElement fill, float ratio)
|
||||||
{
|
{
|
||||||
|
if (fill == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (ratio < 0f)
|
if (ratio < 0f)
|
||||||
{
|
{
|
||||||
ratio = 0f;
|
ratio = 0f;
|
||||||
@ -275,6 +292,11 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
|
|
||||||
fill.style.width = Length.Percent(ratio * 100f);
|
fill.style.width = Length.Percent(ratio * 100f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static float GetScale()
|
||||||
|
{
|
||||||
|
return Instance != null ? Instance.GetUiScale() : 1f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -263,7 +263,7 @@ namespace AlicizaX.Debugger.Runtime
|
|||||||
|
|
||||||
if (!AppServices.TryGetApp<IDebuggerService>(out _mDebuggerService))
|
if (!AppServices.TryGetApp<IDebuggerService>(out _mDebuggerService))
|
||||||
{
|
{
|
||||||
_mDebuggerService = AppServices.RegisterApp(new DebuggerService());
|
_mDebuggerService = AppServices.RegisterApp<IDebuggerService>(new DebuggerService());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_mDebuggerService == null)
|
if (_mDebuggerService == null)
|
||||||
|
|||||||
@ -55,7 +55,7 @@ namespace AlicizaX.Localization.Runtime
|
|||||||
{
|
{
|
||||||
if (!AppServices.TryGetApp<ILocalizationService>(out _mLocalizationService))
|
if (!AppServices.TryGetApp<ILocalizationService>(out _mLocalizationService))
|
||||||
{
|
{
|
||||||
_mLocalizationService = AppServices.RegisterApp(new LocalizationService());
|
_mLocalizationService = AppServices.RegisterApp<ILocalizationService>(new LocalizationService());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_mLocalizationService == null)
|
if (_mLocalizationService == null)
|
||||||
|
|||||||
@ -615,13 +615,11 @@ namespace AlicizaX
|
|||||||
{
|
{
|
||||||
MemoryPool<BenchmarkMemory>.ClearAll();
|
MemoryPool<BenchmarkMemory>.ClearAll();
|
||||||
MemoryPool<BenchmarkMemory>.Prewarm(objectCount);
|
MemoryPool<BenchmarkMemory>.Prewarm(objectCount);
|
||||||
Type type = typeof(BenchmarkMemory);
|
|
||||||
|
|
||||||
RestartCaseMeasure();
|
RestartCaseMeasure();
|
||||||
for (int i = 0; i < loopCount; i++)
|
for (int i = 0; i < loopCount; i++)
|
||||||
{
|
{
|
||||||
IMemory item = MemoryPool.Acquire(type);
|
BenchmarkMemory item = MemoryPool.Acquire<BenchmarkMemory>();
|
||||||
MemoryPool.Release(item);
|
MemoryPool.Release<BenchmarkMemory>(item);
|
||||||
}
|
}
|
||||||
StopCaseMeasure();
|
StopCaseMeasure();
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ namespace AlicizaX
|
|||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_mObjectPoolService = AppServices.RegisterApp(new ObjectPoolService());
|
_mObjectPoolService = AppServices.RegisterApp<IObjectPoolService>(new ObjectPoolService());
|
||||||
Application.lowMemory += OnLowMemory;
|
Application.lowMemory += OnLowMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,16 +32,25 @@ namespace AlicizaX.ObjectPool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectSlot[] m_Slots;
|
private ObjectSlot[][] m_Pages;
|
||||||
private int m_SlotCount;
|
private int[][] m_PageFreeStacks;
|
||||||
private int[] m_FreeStack;
|
private int[] m_PageAliveCounts;
|
||||||
private int m_FreeTop;
|
private int[] m_PageFreeTops;
|
||||||
|
private byte[] m_PageFlags;
|
||||||
|
private int m_PageCount;
|
||||||
|
private int[] m_FreePageStack;
|
||||||
|
private int m_FreePageTop;
|
||||||
|
private int[] m_EmptyPageStack;
|
||||||
|
private int m_EmptyPageTop;
|
||||||
|
private int[] m_ReleasedPageStack;
|
||||||
|
private int m_ReleasedPageTop;
|
||||||
|
|
||||||
private ReferenceOpenHashMap m_TargetMap;
|
private ReferenceOpenHashMap m_TargetMap;
|
||||||
private StringOpenHashMap m_AllNameHeadMap;
|
private StringOpenHashMap m_AllNameHeadMap;
|
||||||
private StringOpenHashMap m_NameCursorMap;
|
private StringOpenHashMap m_NameCursorMap;
|
||||||
|
|
||||||
private readonly bool m_AllowMultiSpawn;
|
private readonly bool m_AllowMultiSpawn;
|
||||||
|
private readonly MemoryPoolHandle m_ObjectMemoryPoolHandle;
|
||||||
private float m_AutoReleaseInterval;
|
private float m_AutoReleaseInterval;
|
||||||
private int m_Capacity;
|
private int m_Capacity;
|
||||||
private float m_ExpireTime;
|
private float m_ExpireTime;
|
||||||
@ -59,18 +68,37 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
private const int DefaultReleasePerFrame = 8;
|
private const int DefaultReleasePerFrame = 8;
|
||||||
private const int InitSlotCapacity = 16;
|
private const int InitSlotCapacity = 16;
|
||||||
|
private const int InitPageCapacity = 4;
|
||||||
|
private const int PageBits = 8;
|
||||||
|
private const int PageSize = 1 << PageBits;
|
||||||
|
private const int PageMask = PageSize - 1;
|
||||||
|
private const byte PageAllocated = 1;
|
||||||
|
private const byte PageInFreeStack = 2;
|
||||||
|
private const byte PageInEmptyStack = 4;
|
||||||
|
private const int EmptyPageReleaseBudget = 1;
|
||||||
|
|
||||||
public ObjectPool(string name, bool allowMultiSpawn,
|
public ObjectPool(string name, bool allowMultiSpawn,
|
||||||
float autoReleaseInterval, int capacity, float expireTime, int priority)
|
float autoReleaseInterval, int capacity, float expireTime, int priority)
|
||||||
: base(name)
|
: base(name)
|
||||||
{
|
{
|
||||||
int initCap = Math.Min(Math.Max(capacity, 1), InitSlotCapacity);
|
int initCap = Math.Min(Math.Max(capacity, 1), InitSlotCapacity);
|
||||||
m_Slots = SlotArrayPool<ObjectSlot>.Rent(initCap);
|
m_Pages = SlotArrayPool<ObjectSlot[]>.Rent(InitPageCapacity);
|
||||||
m_FreeStack = SlotArrayPool<int>.Rent(initCap);
|
m_PageFreeStacks = SlotArrayPool<int[]>.Rent(InitPageCapacity);
|
||||||
|
m_PageAliveCounts = SlotArrayPool<int>.Rent(InitPageCapacity);
|
||||||
|
m_PageFreeTops = SlotArrayPool<int>.Rent(InitPageCapacity);
|
||||||
|
m_PageFlags = SlotArrayPool<byte>.Rent(InitPageCapacity);
|
||||||
|
m_FreePageStack = SlotArrayPool<int>.Rent(InitPageCapacity);
|
||||||
|
m_EmptyPageStack = SlotArrayPool<int>.Rent(InitPageCapacity);
|
||||||
|
m_ReleasedPageStack = SlotArrayPool<int>.Rent(InitPageCapacity);
|
||||||
m_TargetMap = new ReferenceOpenHashMap(initCap);
|
m_TargetMap = new ReferenceOpenHashMap(initCap);
|
||||||
m_AllNameHeadMap = new StringOpenHashMap(initCap);
|
m_AllNameHeadMap = new StringOpenHashMap(initCap);
|
||||||
m_NameCursorMap = new StringOpenHashMap(initCap);
|
m_NameCursorMap = new StringOpenHashMap(initCap);
|
||||||
|
m_PageCount = 0;
|
||||||
|
m_FreePageTop = 0;
|
||||||
|
m_EmptyPageTop = 0;
|
||||||
|
m_ReleasedPageTop = 0;
|
||||||
m_AllowMultiSpawn = allowMultiSpawn;
|
m_AllowMultiSpawn = allowMultiSpawn;
|
||||||
|
m_ObjectMemoryPoolHandle = MemoryPool.GetHandle(typeof(T));
|
||||||
m_AutoReleaseInterval = autoReleaseInterval;
|
m_AutoReleaseInterval = autoReleaseInterval;
|
||||||
m_Capacity = capacity;
|
m_Capacity = capacity;
|
||||||
m_ExpireTime = expireTime;
|
m_ExpireTime = expireTime;
|
||||||
@ -155,7 +183,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
if (obj == null) return;
|
if (obj == null) return;
|
||||||
if (obj.Target == null) return;
|
if (obj.Target == null) return;
|
||||||
|
|
||||||
if (m_TargetMap.TryGetValue(obj.Target, out int existingIdx) && m_Slots[existingIdx].IsAlive())
|
if (m_TargetMap.TryGetValue(obj.Target, out int existingIdx) && GetSlotRef(existingIdx).IsAlive())
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
UnityEngine.Debug.LogError($"Target '{obj.Target.GetType().FullName}' is already registered in pool '{FullName}'.");
|
UnityEngine.Debug.LogError($"Target '{obj.Target.GetType().FullName}' is already registered in pool '{FullName}'.");
|
||||||
@ -175,7 +203,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref GetSlotRef(idx);
|
||||||
slot.Obj = obj;
|
slot.Obj = obj;
|
||||||
slot.SpawnCount = spawned ? 1 : 0;
|
slot.SpawnCount = spawned ? 1 : 0;
|
||||||
slot.LastUseTime = Time.realtimeSinceStartup;
|
slot.LastUseTime = Time.realtimeSinceStartup;
|
||||||
@ -190,7 +218,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
string objectName = obj.Name ?? string.Empty;
|
string objectName = obj.Name ?? string.Empty;
|
||||||
if (m_AllNameHeadMap.TryGetValue(objectName, out int existingHead))
|
if (m_AllNameHeadMap.TryGetValue(objectName, out int existingHead))
|
||||||
{
|
{
|
||||||
m_Slots[existingHead].PrevByName = idx;
|
GetSlotRef(existingHead).PrevByName = idx;
|
||||||
slot.NextByName = existingHead;
|
slot.NextByName = existingHead;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -221,7 +249,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
int head = FindAvailableByName(name);
|
int head = FindAvailableByName(name);
|
||||||
if (head < 0) return null;
|
if (head < 0) return null;
|
||||||
|
|
||||||
ref var slot = ref m_Slots[head];
|
ref var slot = ref GetSlotRef(head);
|
||||||
if (!slot.IsAlive() || slot.SpawnCount != 0 || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
|
if (!slot.IsAlive() || slot.SpawnCount != 0 || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
@ -253,7 +281,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
if (!m_AllNameHeadMap.TryGetValue(name, out int head))
|
if (!m_AllNameHeadMap.TryGetValue(name, out int head))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
ref var headSlot = ref m_Slots[head];
|
ref var headSlot = ref GetSlotRef(head);
|
||||||
if (headSlot.IsAlive()
|
if (headSlot.IsAlive()
|
||||||
&& headSlot.SpawnCount == 0
|
&& headSlot.SpawnCount == 0
|
||||||
&& string.Equals(headSlot.Obj.Name, name, StringComparison.Ordinal))
|
&& string.Equals(headSlot.Obj.Name, name, StringComparison.Ordinal))
|
||||||
@ -269,7 +297,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[current];
|
ref var slot = ref GetSlotRef(current);
|
||||||
if (slot.IsAlive()
|
if (slot.IsAlive()
|
||||||
&& slot.SpawnCount == 0
|
&& slot.SpawnCount == 0
|
||||||
&& string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
|
&& string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
|
||||||
@ -288,11 +316,9 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
private int GetValidNameCursor(string name, int head)
|
private int GetValidNameCursor(string name, int head)
|
||||||
{
|
{
|
||||||
if (m_NameCursorMap.TryGetValue(name, out int cursor)
|
if (m_NameCursorMap.TryGetValue(name, out int cursor) && IsValidIndex(cursor))
|
||||||
&& cursor >= 0
|
|
||||||
&& cursor < m_SlotCount)
|
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[cursor];
|
ref var slot = ref GetSlotRef(cursor);
|
||||||
if (slot.IsAlive() && string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
|
if (slot.IsAlive() && string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
@ -339,8 +365,8 @@ namespace AlicizaX.ObjectPool
|
|||||||
int current = m_UnusedHead;
|
int current = m_UnusedHead;
|
||||||
while (current >= 0)
|
while (current >= 0)
|
||||||
{
|
{
|
||||||
int next = m_Slots[current].NextUnused;
|
int next = GetSlotRef(current).NextUnused;
|
||||||
ref var slot = ref m_Slots[current];
|
ref var slot = ref GetSlotRef(current);
|
||||||
if (CanReleaseSlot(ref slot))
|
if (CanReleaseSlot(ref slot))
|
||||||
{
|
{
|
||||||
ReleaseSlot(current, false);
|
ReleaseSlot(current, false);
|
||||||
@ -352,8 +378,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
if (released > 0)
|
if (released > 0)
|
||||||
{
|
{
|
||||||
m_PendingReleaseCount = Math.Max(0, m_PendingReleaseCount - released);
|
m_PendingReleaseCount = Math.Max(0, m_PendingReleaseCount - released);
|
||||||
TrimSlotCountTail();
|
ReleaseEmptyPages(EmptyPageReleaseBudget);
|
||||||
ShrinkStorageIfEmpty();
|
|
||||||
UpdateActiveState();
|
UpdateActiveState();
|
||||||
ValidateState();
|
ValidateState();
|
||||||
}
|
}
|
||||||
@ -400,66 +425,39 @@ namespace AlicizaX.ObjectPool
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
m_ShrinkCounter = 0;
|
m_ShrinkCounter = 0;
|
||||||
|
ReleaseEmptyPages(EmptyPageReleaseBudget);
|
||||||
TrimSlotCountTail();
|
|
||||||
|
|
||||||
int slotArrayLen = m_Slots.Length;
|
|
||||||
int aliveCount = m_TargetMap.Count;
|
|
||||||
if (aliveCount == 0 || slotArrayLen <= InitSlotCapacity)
|
|
||||||
return;
|
|
||||||
|
|
||||||
float usageRatio = (float)aliveCount / slotArrayLen;
|
|
||||||
if (usageRatio < 0.25f)
|
|
||||||
{
|
|
||||||
int targetCapacity = Math.Max(NextPowerOf2(Math.Max(m_SlotCount, aliveCount)), InitSlotCapacity);
|
|
||||||
if (targetCapacity < slotArrayLen && m_SlotCount <= targetCapacity)
|
|
||||||
{
|
|
||||||
var newSlots = SlotArrayPool<ObjectSlot>.Rent(targetCapacity);
|
|
||||||
var newFreeStack = SlotArrayPool<int>.Rent(targetCapacity);
|
|
||||||
|
|
||||||
Array.Copy(m_Slots, 0, newSlots, 0, m_SlotCount);
|
|
||||||
|
|
||||||
int newFreeTop = 0;
|
|
||||||
for (int i = 0; i < m_FreeTop; i++)
|
|
||||||
{
|
|
||||||
if (m_FreeStack[i] < targetCapacity)
|
|
||||||
newFreeStack[newFreeTop++] = m_FreeStack[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
|
|
||||||
SlotArrayPool<int>.Return(m_FreeStack, true);
|
|
||||||
|
|
||||||
m_Slots = newSlots;
|
|
||||||
m_FreeStack = newFreeStack;
|
|
||||||
m_FreeTop = newFreeTop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Shutdown()
|
internal override void Shutdown()
|
||||||
{
|
{
|
||||||
m_IsShuttingDown = true;
|
m_IsShuttingDown = true;
|
||||||
for (int i = 0; i < m_SlotCount; i++)
|
for (int page = 0; page < m_PageCount; page++)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[i];
|
ObjectSlot[] slots = m_Pages[page];
|
||||||
if (!slot.IsAlive()) continue;
|
if (slots == null) continue;
|
||||||
slot.Obj.Release(true);
|
|
||||||
MemoryPool.Release(slot.Obj);
|
for (int offset = 0; offset < PageSize; offset++)
|
||||||
slot.Obj = null;
|
{
|
||||||
slot.SetAlive(false);
|
ref var slot = ref slots[offset];
|
||||||
|
if (!slot.IsAlive()) continue;
|
||||||
|
slot.Obj.Release(true);
|
||||||
|
m_ObjectMemoryPoolHandle.Release(slot.Obj);
|
||||||
|
slot.Obj = null;
|
||||||
|
slot.SetAlive(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_TargetMap.Clear();
|
m_TargetMap.Clear();
|
||||||
m_AllNameHeadMap.Clear();
|
m_AllNameHeadMap.Clear();
|
||||||
m_NameCursorMap.Clear();
|
m_NameCursorMap.Clear();
|
||||||
|
|
||||||
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
|
ReleaseAllPages();
|
||||||
SlotArrayPool<int>.Return(m_FreeStack, true);
|
ReturnPageStorage();
|
||||||
m_Slots = null;
|
|
||||||
m_FreeStack = null;
|
|
||||||
|
|
||||||
m_SlotCount = 0;
|
m_PageCount = 0;
|
||||||
m_FreeTop = 0;
|
m_FreePageTop = 0;
|
||||||
|
m_EmptyPageTop = 0;
|
||||||
|
m_ReleasedPageTop = 0;
|
||||||
m_PendingReleaseCount = 0;
|
m_PendingReleaseCount = 0;
|
||||||
m_UnusedHead = -1;
|
m_UnusedHead = -1;
|
||||||
m_UnusedTail = -1;
|
m_UnusedTail = -1;
|
||||||
@ -480,19 +478,25 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
int write = 0;
|
int write = 0;
|
||||||
int capacity = results.Length;
|
int capacity = results.Length;
|
||||||
for (int i = 0; i < m_SlotCount; i++)
|
for (int page = 0; page < m_PageCount; page++)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[i];
|
ObjectSlot[] slots = m_Pages[page];
|
||||||
if (!slot.IsAlive()) continue;
|
if (slots == null) continue;
|
||||||
|
|
||||||
if (write < capacity)
|
for (int offset = 0; offset < PageSize; offset++)
|
||||||
{
|
{
|
||||||
results[write] = new ObjectInfo(slot.Obj.Name, slot.Obj.Locked,
|
ref var slot = ref slots[offset];
|
||||||
slot.Obj.CustomCanReleaseFlag,
|
if (!slot.IsAlive()) continue;
|
||||||
slot.Obj.LastUseTime, slot.SpawnCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
write++;
|
if (write < capacity)
|
||||||
|
{
|
||||||
|
results[write] = new ObjectInfo(slot.Obj.Name, slot.Obj.Locked,
|
||||||
|
slot.Obj.CustomCanReleaseFlag,
|
||||||
|
slot.Obj.LastUseTime, slot.SpawnCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
write++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return write;
|
return write;
|
||||||
@ -506,7 +510,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void SpawnSlot(int idx, float now)
|
private void SpawnSlot(int idx, float now)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref GetSlotRef(idx);
|
||||||
if (slot.SpawnCount == 0)
|
if (slot.SpawnCount == 0)
|
||||||
MarkSlotUnavailable(idx);
|
MarkSlotUnavailable(idx);
|
||||||
|
|
||||||
@ -519,7 +523,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void UnspawnSlot(int idx)
|
private void UnspawnSlot(int idx)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref GetSlotRef(idx);
|
||||||
float now = Time.realtimeSinceStartup;
|
float now = Time.realtimeSinceStartup;
|
||||||
slot.LastUseTime = now;
|
slot.LastUseTime = now;
|
||||||
slot.Obj.LastUseTime = now;
|
slot.Obj.LastUseTime = now;
|
||||||
@ -548,43 +552,65 @@ namespace AlicizaX.ObjectPool
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private int AllocSlot()
|
private int AllocSlot()
|
||||||
{
|
{
|
||||||
if (m_FreeTop > 0)
|
int page = GetWritablePage();
|
||||||
return m_FreeStack[--m_FreeTop];
|
if (page < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
if (m_SlotCount >= m_Slots.Length)
|
int freeTop = m_PageFreeTops[page] - 1;
|
||||||
{
|
int offset = m_PageFreeStacks[page][freeTop];
|
||||||
GrowSlots();
|
m_PageFreeTops[page] = freeTop;
|
||||||
if (m_SlotCount >= m_Slots.Length)
|
m_PageAliveCounts[page]++;
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_SlotCount++;
|
if (freeTop == 0)
|
||||||
|
RemoveFreePage(page);
|
||||||
|
|
||||||
|
return MakeIndex(page, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GrowSlots()
|
private int GetWritablePage()
|
||||||
{
|
{
|
||||||
int currentCap = m_Slots.Length;
|
while (m_FreePageTop > 0)
|
||||||
int maxCap = m_Capacity == int.MaxValue ? int.MaxValue : Math.Max(m_Capacity, InitSlotCapacity);
|
{
|
||||||
int newCap = Math.Min(Math.Max(currentCap * 2, InitSlotCapacity), maxCap);
|
int page = m_FreePageStack[m_FreePageTop - 1];
|
||||||
if (newCap <= currentCap)
|
if (IsAllocatedPage(page) && m_PageFreeTops[page] > 0)
|
||||||
return;
|
return page;
|
||||||
|
|
||||||
var newSlots = SlotArrayPool<ObjectSlot>.Rent(newCap);
|
m_FreePageTop--;
|
||||||
var newFreeStack = SlotArrayPool<int>.Rent(newCap);
|
if (page >= 0 && page < m_PageFlags.Length)
|
||||||
|
m_PageFlags[page] = (byte)(m_PageFlags[page] & ~PageInFreeStack);
|
||||||
|
}
|
||||||
|
|
||||||
Array.Copy(m_Slots, 0, newSlots, 0, m_SlotCount);
|
return AllocatePage();
|
||||||
Array.Copy(m_FreeStack, 0, newFreeStack, 0, m_FreeTop);
|
}
|
||||||
|
|
||||||
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
|
private int AllocatePage()
|
||||||
SlotArrayPool<int>.Return(m_FreeStack, true);
|
{
|
||||||
|
int page;
|
||||||
|
if (m_ReleasedPageTop > 0)
|
||||||
|
{
|
||||||
|
page = m_ReleasedPageStack[--m_ReleasedPageTop];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
page = m_PageCount++;
|
||||||
|
EnsurePageStorageCapacity(m_PageCount);
|
||||||
|
}
|
||||||
|
|
||||||
m_Slots = newSlots;
|
m_Pages[page] = SlotArrayPool<ObjectSlot>.Rent(PageSize);
|
||||||
m_FreeStack = newFreeStack;
|
m_PageFreeStacks[page] = SlotArrayPool<int>.Rent(PageSize);
|
||||||
|
for (int offset = 0; offset < PageSize; offset++)
|
||||||
|
m_PageFreeStacks[page][offset] = offset;
|
||||||
|
|
||||||
|
m_PageAliveCounts[page] = 0;
|
||||||
|
m_PageFreeTops[page] = PageSize;
|
||||||
|
m_PageFlags[page] = PageAllocated;
|
||||||
|
AddFreePage(page);
|
||||||
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReleaseSlot(int idx, bool compactStorage = true)
|
private void ReleaseSlot(int idx, bool compactStorage = true)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref GetSlotRef(idx);
|
||||||
if (!slot.IsAlive()) return;
|
if (!slot.IsAlive()) return;
|
||||||
if (slot.SpawnCount > 0) return;
|
if (slot.SpawnCount > 0) return;
|
||||||
|
|
||||||
@ -595,7 +621,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
m_TargetMap.Remove(obj.Target);
|
m_TargetMap.Remove(obj.Target);
|
||||||
|
|
||||||
obj.Release(false);
|
obj.Release(false);
|
||||||
MemoryPool.Release(obj);
|
m_ObjectMemoryPoolHandle.Release(obj);
|
||||||
|
|
||||||
slot.Obj = null;
|
slot.Obj = null;
|
||||||
slot.SetAlive(false);
|
slot.SetAlive(false);
|
||||||
@ -605,21 +631,19 @@ namespace AlicizaX.ObjectPool
|
|||||||
slot.PrevUnused = -1;
|
slot.PrevUnused = -1;
|
||||||
slot.NextUnused = -1;
|
slot.NextUnused = -1;
|
||||||
|
|
||||||
if (m_FreeTop >= m_FreeStack.Length)
|
int page = PageOf(idx);
|
||||||
{
|
int offset = OffsetOf(idx);
|
||||||
int newCap = m_FreeStack.Length * 2;
|
m_PageFreeStacks[page][m_PageFreeTops[page]++] = offset;
|
||||||
var newFreeStack = SlotArrayPool<int>.Rent(newCap);
|
m_PageAliveCounts[page]--;
|
||||||
Array.Copy(m_FreeStack, 0, newFreeStack, 0, m_FreeTop);
|
|
||||||
SlotArrayPool<int>.Return(m_FreeStack, true);
|
if (m_PageFreeTops[page] == 1)
|
||||||
m_FreeStack = newFreeStack;
|
AddFreePage(page);
|
||||||
}
|
|
||||||
m_FreeStack[m_FreeTop++] = idx;
|
if (m_PageAliveCounts[page] == 0)
|
||||||
|
AddEmptyPageCandidate(page);
|
||||||
|
|
||||||
if (compactStorage)
|
if (compactStorage)
|
||||||
{
|
ReleaseEmptyPages(EmptyPageReleaseBudget);
|
||||||
TrimSlotCountTail();
|
|
||||||
ShrinkStorageIfEmpty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool EnsureRegisterCapacity()
|
private bool EnsureRegisterCapacity()
|
||||||
@ -637,36 +661,194 @@ namespace AlicizaX.ObjectPool
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrimSlotCountTail()
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static int MakeIndex(int page, int offset)
|
||||||
{
|
{
|
||||||
while (m_SlotCount > 0 && !m_Slots[m_SlotCount - 1].IsAlive())
|
return (page << PageBits) | offset;
|
||||||
m_SlotCount--;
|
|
||||||
|
|
||||||
int write = 0;
|
|
||||||
for (int i = 0; i < m_FreeTop; i++)
|
|
||||||
{
|
|
||||||
int freeIndex = m_FreeStack[i];
|
|
||||||
if (freeIndex < m_SlotCount)
|
|
||||||
m_FreeStack[write++] = freeIndex;
|
|
||||||
}
|
|
||||||
m_FreeTop = write;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int NextPowerOf2(int value)
|
private static int PageOf(int index)
|
||||||
{
|
{
|
||||||
value--;
|
return index >> PageBits;
|
||||||
value |= value >> 1;
|
}
|
||||||
value |= value >> 2;
|
|
||||||
value |= value >> 4;
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
value |= value >> 8;
|
private static int OffsetOf(int index)
|
||||||
value |= value >> 16;
|
{
|
||||||
return value + 1;
|
return index & PageMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private ref ObjectSlot GetSlotRef(int index)
|
||||||
|
{
|
||||||
|
return ref m_Pages[index >> PageBits][index & PageMask];
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool IsAllocatedPage(int page)
|
||||||
|
{
|
||||||
|
return page >= 0
|
||||||
|
&& page < m_PageCount
|
||||||
|
&& (m_PageFlags[page] & PageAllocated) != 0
|
||||||
|
&& m_Pages[page] != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsurePageStorageCapacity(int required)
|
||||||
|
{
|
||||||
|
if (required <= m_Pages.Length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int newCap = Math.Max(required, m_Pages.Length * 2);
|
||||||
|
var newPages = SlotArrayPool<ObjectSlot[]>.Rent(newCap);
|
||||||
|
var newPageFreeStacks = SlotArrayPool<int[]>.Rent(newCap);
|
||||||
|
var newPageAliveCounts = SlotArrayPool<int>.Rent(newCap);
|
||||||
|
var newPageFreeTops = SlotArrayPool<int>.Rent(newCap);
|
||||||
|
var newPageFlags = SlotArrayPool<byte>.Rent(newCap);
|
||||||
|
var newFreePageStack = SlotArrayPool<int>.Rent(newCap);
|
||||||
|
var newEmptyPageStack = SlotArrayPool<int>.Rent(newCap);
|
||||||
|
var newReleasedPageStack = SlotArrayPool<int>.Rent(newCap);
|
||||||
|
|
||||||
|
Array.Copy(m_Pages, 0, newPages, 0, m_PageCount);
|
||||||
|
Array.Copy(m_PageFreeStacks, 0, newPageFreeStacks, 0, m_PageCount);
|
||||||
|
Array.Copy(m_PageAliveCounts, 0, newPageAliveCounts, 0, m_PageCount);
|
||||||
|
Array.Copy(m_PageFreeTops, 0, newPageFreeTops, 0, m_PageCount);
|
||||||
|
Array.Copy(m_PageFlags, 0, newPageFlags, 0, m_PageCount);
|
||||||
|
Array.Copy(m_FreePageStack, 0, newFreePageStack, 0, m_FreePageTop);
|
||||||
|
Array.Copy(m_EmptyPageStack, 0, newEmptyPageStack, 0, m_EmptyPageTop);
|
||||||
|
Array.Copy(m_ReleasedPageStack, 0, newReleasedPageStack, 0, m_ReleasedPageTop);
|
||||||
|
|
||||||
|
SlotArrayPool<ObjectSlot[]>.Return(m_Pages, true);
|
||||||
|
SlotArrayPool<int[]>.Return(m_PageFreeStacks, true);
|
||||||
|
SlotArrayPool<int>.Return(m_PageAliveCounts, true);
|
||||||
|
SlotArrayPool<int>.Return(m_PageFreeTops, true);
|
||||||
|
SlotArrayPool<byte>.Return(m_PageFlags, true);
|
||||||
|
SlotArrayPool<int>.Return(m_FreePageStack, true);
|
||||||
|
SlotArrayPool<int>.Return(m_EmptyPageStack, true);
|
||||||
|
SlotArrayPool<int>.Return(m_ReleasedPageStack, true);
|
||||||
|
|
||||||
|
m_Pages = newPages;
|
||||||
|
m_PageFreeStacks = newPageFreeStacks;
|
||||||
|
m_PageAliveCounts = newPageAliveCounts;
|
||||||
|
m_PageFreeTops = newPageFreeTops;
|
||||||
|
m_PageFlags = newPageFlags;
|
||||||
|
m_FreePageStack = newFreePageStack;
|
||||||
|
m_EmptyPageStack = newEmptyPageStack;
|
||||||
|
m_ReleasedPageStack = newReleasedPageStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void EnsurePageIndexStackCapacity(ref int[] stack, int required)
|
||||||
|
{
|
||||||
|
if (required <= stack.Length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int newCap = Math.Max(required, stack.Length * 2);
|
||||||
|
var newStack = SlotArrayPool<int>.Rent(newCap);
|
||||||
|
Array.Copy(stack, 0, newStack, 0, stack.Length);
|
||||||
|
SlotArrayPool<int>.Return(stack, true);
|
||||||
|
stack = newStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFreePage(int page)
|
||||||
|
{
|
||||||
|
if ((m_PageFlags[page] & PageInFreeStack) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_PageFlags[page] = (byte)(m_PageFlags[page] | PageInFreeStack);
|
||||||
|
EnsurePageIndexStackCapacity(ref m_FreePageStack, m_FreePageTop + 1);
|
||||||
|
m_FreePageStack[m_FreePageTop++] = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveFreePage(int page)
|
||||||
|
{
|
||||||
|
if ((m_PageFlags[page] & PageInFreeStack) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_PageFlags[page] = (byte)(m_PageFlags[page] & ~PageInFreeStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddEmptyPageCandidate(int page)
|
||||||
|
{
|
||||||
|
if ((m_PageFlags[page] & PageInEmptyStack) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_PageFlags[page] = (byte)(m_PageFlags[page] | PageInEmptyStack);
|
||||||
|
EnsurePageIndexStackCapacity(ref m_EmptyPageStack, m_EmptyPageTop + 1);
|
||||||
|
m_EmptyPageStack[m_EmptyPageTop++] = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseEmptyPages(int budget)
|
||||||
|
{
|
||||||
|
while (budget > 0 && m_EmptyPageTop > 0)
|
||||||
|
{
|
||||||
|
int page = m_EmptyPageStack[--m_EmptyPageTop];
|
||||||
|
m_PageFlags[page] = (byte)(m_PageFlags[page] & ~PageInEmptyStack);
|
||||||
|
if (!IsAllocatedPage(page) || m_PageAliveCounts[page] != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ReleasePage(page);
|
||||||
|
AddReleasedPage(page);
|
||||||
|
budget--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddReleasedPage(int page)
|
||||||
|
{
|
||||||
|
EnsurePageIndexStackCapacity(ref m_ReleasedPageStack, m_ReleasedPageTop + 1);
|
||||||
|
m_ReleasedPageStack[m_ReleasedPageTop++] = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleasePage(int page)
|
||||||
|
{
|
||||||
|
SlotArrayPool<ObjectSlot>.Return(m_Pages[page], true);
|
||||||
|
SlotArrayPool<int>.Return(m_PageFreeStacks[page], true);
|
||||||
|
m_Pages[page] = null;
|
||||||
|
m_PageFreeStacks[page] = null;
|
||||||
|
m_PageAliveCounts[page] = 0;
|
||||||
|
m_PageFreeTops[page] = 0;
|
||||||
|
m_PageFlags[page] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseAllPages()
|
||||||
|
{
|
||||||
|
for (int page = 0; page < m_PageCount; page++)
|
||||||
|
{
|
||||||
|
if (m_Pages[page] != null)
|
||||||
|
SlotArrayPool<ObjectSlot>.Return(m_Pages[page], true);
|
||||||
|
if (m_PageFreeStacks[page] != null)
|
||||||
|
SlotArrayPool<int>.Return(m_PageFreeStacks[page], true);
|
||||||
|
m_Pages[page] = null;
|
||||||
|
m_PageFreeStacks[page] = null;
|
||||||
|
m_PageAliveCounts[page] = 0;
|
||||||
|
m_PageFreeTops[page] = 0;
|
||||||
|
m_PageFlags[page] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReturnPageStorage()
|
||||||
|
{
|
||||||
|
SlotArrayPool<ObjectSlot[]>.Return(m_Pages, true);
|
||||||
|
SlotArrayPool<int[]>.Return(m_PageFreeStacks, true);
|
||||||
|
SlotArrayPool<int>.Return(m_PageAliveCounts, true);
|
||||||
|
SlotArrayPool<int>.Return(m_PageFreeTops, true);
|
||||||
|
SlotArrayPool<byte>.Return(m_PageFlags, true);
|
||||||
|
SlotArrayPool<int>.Return(m_FreePageStack, true);
|
||||||
|
SlotArrayPool<int>.Return(m_EmptyPageStack, true);
|
||||||
|
SlotArrayPool<int>.Return(m_ReleasedPageStack, true);
|
||||||
|
m_Pages = null;
|
||||||
|
m_PageFreeStacks = null;
|
||||||
|
m_PageAliveCounts = null;
|
||||||
|
m_PageFreeTops = null;
|
||||||
|
m_PageFlags = null;
|
||||||
|
m_FreePageStack = null;
|
||||||
|
m_EmptyPageStack = null;
|
||||||
|
m_ReleasedPageStack = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveFromAllNameChain(int idx)
|
private void RemoveFromAllNameChain(int idx)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref GetSlotRef(idx);
|
||||||
string objectName = slot.Obj.Name ?? string.Empty;
|
string objectName = slot.Obj.Name ?? string.Empty;
|
||||||
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
|
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
|
||||||
return;
|
return;
|
||||||
@ -675,7 +857,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
int next = slot.NextByName;
|
int next = slot.NextByName;
|
||||||
if (prev >= 0)
|
if (prev >= 0)
|
||||||
{
|
{
|
||||||
m_Slots[prev].NextByName = next;
|
GetSlotRef(prev).NextByName = next;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -701,7 +883,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (next >= 0)
|
if (next >= 0)
|
||||||
m_Slots[next].PrevByName = prev;
|
GetSlotRef(next).PrevByName = prev;
|
||||||
|
|
||||||
slot.PrevByName = -1;
|
slot.PrevByName = -1;
|
||||||
slot.NextByName = -1;
|
slot.NextByName = -1;
|
||||||
@ -714,7 +896,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
while (current >= 0 && released < maxReleaseCount)
|
while (current >= 0 && released < maxReleaseCount)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[current];
|
ref var slot = ref GetSlotRef(current);
|
||||||
int next = slot.NextUnused;
|
int next = slot.NextUnused;
|
||||||
|
|
||||||
if (requireExpired && slot.LastUseTime > expireThreshold)
|
if (requireExpired && slot.LastUseTime > expireThreshold)
|
||||||
@ -738,10 +920,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (released > 0)
|
if (released > 0)
|
||||||
{
|
ReleaseEmptyPages(EmptyPageReleaseBudget);
|
||||||
TrimSlotCountTail();
|
|
||||||
ShrinkStorageIfEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return released;
|
return released;
|
||||||
}
|
}
|
||||||
@ -756,7 +935,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
{
|
{
|
||||||
if (m_LastBudgetScanStart >= 0)
|
if (m_LastBudgetScanStart >= 0)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[m_LastBudgetScanStart];
|
ref var slot = ref GetSlotRef(m_LastBudgetScanStart);
|
||||||
if (slot.IsAlive() && slot.SpawnCount == 0)
|
if (slot.IsAlive() && slot.SpawnCount == 0)
|
||||||
{
|
{
|
||||||
return m_LastBudgetScanStart;
|
return m_LastBudgetScanStart;
|
||||||
@ -775,23 +954,12 @@ namespace AlicizaX.ObjectPool
|
|||||||
&& slot.Obj.CustomCanReleaseFlag;
|
&& slot.Obj.CustomCanReleaseFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShrinkStorageIfEmpty()
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool IsValidIndex(int index)
|
||||||
{
|
{
|
||||||
if (m_TargetMap.Count > 0 || m_Slots.Length <= InitSlotCapacity)
|
int page = index >> PageBits;
|
||||||
return;
|
int offset = index & PageMask;
|
||||||
|
return offset < PageSize && IsAllocatedPage(page);
|
||||||
SlotArrayPool<ObjectSlot>.Return(m_Slots, true);
|
|
||||||
SlotArrayPool<int>.Return(m_FreeStack, true);
|
|
||||||
|
|
||||||
m_Slots = SlotArrayPool<ObjectSlot>.Rent(InitSlotCapacity);
|
|
||||||
m_FreeStack = SlotArrayPool<int>.Rent(InitSlotCapacity);
|
|
||||||
m_AllNameHeadMap.Clear();
|
|
||||||
m_NameCursorMap.Clear();
|
|
||||||
m_SlotCount = 0;
|
|
||||||
m_FreeTop = 0;
|
|
||||||
m_UnusedHead = -1;
|
|
||||||
m_UnusedTail = -1;
|
|
||||||
m_LastBudgetScanStart = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Conditional("UNITY_EDITOR")]
|
[Conditional("UNITY_EDITOR")]
|
||||||
@ -800,53 +968,60 @@ namespace AlicizaX.ObjectPool
|
|||||||
#if UNITY_EDITOR && ENABLE_OBJECTPOOL_VALIDATION
|
#if UNITY_EDITOR && ENABLE_OBJECTPOOL_VALIDATION
|
||||||
int aliveCount = 0;
|
int aliveCount = 0;
|
||||||
int unusedCount = 0;
|
int unusedCount = 0;
|
||||||
for (int i = 0; i < m_SlotCount; i++)
|
for (int page = 0; page < m_PageCount; page++)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[i];
|
ObjectSlot[] slots = m_Pages[page];
|
||||||
if (!slot.IsAlive())
|
if (slots == null) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
aliveCount++;
|
for (int offset = 0; offset < PageSize; offset++)
|
||||||
|
|
||||||
object target = slot.Obj.Target;
|
|
||||||
if (!m_TargetMap.TryGetValue(target, out int mappedIdx) || mappedIdx != i)
|
|
||||||
{
|
{
|
||||||
UnityEngine.Debug.LogError($"Object pool '{FullName}' target index map is inconsistent.");
|
int idx = MakeIndex(page, offset);
|
||||||
continue;
|
ref var slot = ref slots[offset];
|
||||||
}
|
if (!slot.IsAlive())
|
||||||
|
continue;
|
||||||
|
|
||||||
string objectName = slot.Obj.Name ?? string.Empty;
|
aliveCount++;
|
||||||
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name head is missing.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slot.PrevByName < 0 && head != i)
|
object target = slot.Obj.Target;
|
||||||
{
|
if (!m_TargetMap.TryGetValue(target, out int mappedIdx) || mappedIdx != idx)
|
||||||
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain head is inconsistent.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slot.NextByName >= 0 && m_Slots[slot.NextByName].PrevByName != i)
|
|
||||||
{
|
|
||||||
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain link is inconsistent.");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool inUnusedList = m_UnusedHead == i || slot.PrevUnused >= 0 || slot.NextUnused >= 0;
|
|
||||||
|
|
||||||
if (slot.SpawnCount == 0)
|
|
||||||
{
|
|
||||||
unusedCount++;
|
|
||||||
if (!inUnusedList)
|
|
||||||
{
|
{
|
||||||
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused list is inconsistent.");
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' target index map is inconsistent.");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
string objectName = slot.Obj.Name ?? string.Empty;
|
||||||
{
|
if (!m_AllNameHeadMap.TryGetValue(objectName, out int head))
|
||||||
if (inUnusedList)
|
|
||||||
{
|
{
|
||||||
UnityEngine.Debug.LogError($"Object pool '{FullName}' spawned object exists in unused list.");
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name head is missing.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot.PrevByName < 0 && head != idx)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain head is inconsistent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot.NextByName >= 0 && GetSlotRef(slot.NextByName).PrevByName != idx)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' all-name chain link is inconsistent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inUnusedList = m_UnusedHead == idx || slot.PrevUnused >= 0 || slot.NextUnused >= 0;
|
||||||
|
|
||||||
|
if (slot.SpawnCount == 0)
|
||||||
|
{
|
||||||
|
unusedCount++;
|
||||||
|
if (!inUnusedList)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused list is inconsistent.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (inUnusedList)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' spawned object exists in unused list.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -861,7 +1036,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
int prevUnused = -1;
|
int prevUnused = -1;
|
||||||
while (current >= 0)
|
while (current >= 0)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[current];
|
ref var slot = ref GetSlotRef(current);
|
||||||
if (!slot.IsAlive() || slot.SpawnCount != 0)
|
if (!slot.IsAlive() || slot.SpawnCount != 0)
|
||||||
{
|
{
|
||||||
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused chain contains invalid slot.");
|
UnityEngine.Debug.LogError($"Object pool '{FullName}' unused chain contains invalid slot.");
|
||||||
@ -886,7 +1061,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
private void MarkSlotAvailable(int idx)
|
private void MarkSlotAvailable(int idx)
|
||||||
{
|
{
|
||||||
AddToUnusedListTail(idx);
|
AddToUnusedListTail(idx);
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref GetSlotRef(idx);
|
||||||
if (slot.IsAlive())
|
if (slot.IsAlive())
|
||||||
m_NameCursorMap.AddOrUpdate(slot.Obj.Name ?? string.Empty, idx);
|
m_NameCursorMap.AddOrUpdate(slot.Obj.Name ?? string.Empty, idx);
|
||||||
}
|
}
|
||||||
@ -898,7 +1073,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
private void AddToUnusedListTail(int idx)
|
private void AddToUnusedListTail(int idx)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref GetSlotRef(idx);
|
||||||
if (m_UnusedHead == idx || slot.PrevUnused >= 0 || slot.NextUnused >= 0)
|
if (m_UnusedHead == idx || slot.PrevUnused >= 0 || slot.NextUnused >= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -906,7 +1081,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
slot.NextUnused = -1;
|
slot.NextUnused = -1;
|
||||||
|
|
||||||
if (m_UnusedTail >= 0)
|
if (m_UnusedTail >= 0)
|
||||||
m_Slots[m_UnusedTail].NextUnused = idx;
|
GetSlotRef(m_UnusedTail).NextUnused = idx;
|
||||||
else
|
else
|
||||||
m_UnusedHead = idx;
|
m_UnusedHead = idx;
|
||||||
|
|
||||||
@ -915,7 +1090,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
|
|
||||||
private void RemoveFromUnusedList(int idx)
|
private void RemoveFromUnusedList(int idx)
|
||||||
{
|
{
|
||||||
ref var slot = ref m_Slots[idx];
|
ref var slot = ref GetSlotRef(idx);
|
||||||
if (m_UnusedHead != idx && slot.PrevUnused < 0 && slot.NextUnused < 0)
|
if (m_UnusedHead != idx && slot.PrevUnused < 0 && slot.NextUnused < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -923,12 +1098,12 @@ namespace AlicizaX.ObjectPool
|
|||||||
int next = slot.NextUnused;
|
int next = slot.NextUnused;
|
||||||
|
|
||||||
if (prev >= 0)
|
if (prev >= 0)
|
||||||
m_Slots[prev].NextUnused = next;
|
GetSlotRef(prev).NextUnused = next;
|
||||||
else
|
else
|
||||||
m_UnusedHead = next;
|
m_UnusedHead = next;
|
||||||
|
|
||||||
if (next >= 0)
|
if (next >= 0)
|
||||||
m_Slots[next].PrevUnused = prev;
|
GetSlotRef(next).PrevUnused = prev;
|
||||||
else
|
else
|
||||||
m_UnusedTail = prev;
|
m_UnusedTail = prev;
|
||||||
|
|
||||||
@ -945,7 +1120,7 @@ namespace AlicizaX.ObjectPool
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
float now = Time.realtimeSinceStartup;
|
float now = Time.realtimeSinceStartup;
|
||||||
ref var slot = ref m_Slots[head];
|
ref var slot = ref GetSlotRef(head);
|
||||||
if (!slot.IsAlive() || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
|
if (!slot.IsAlive() || !string.Equals(slot.Obj.Name, name, StringComparison.Ordinal))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|||||||
@ -151,7 +151,7 @@ namespace AlicizaX.Resource.Runtime
|
|||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_resourceService = AppServices.RegisterApp(new ResourceService());
|
_resourceService = AppServices.RegisterApp<IResourceService>(new ResourceService());
|
||||||
|
|
||||||
Application.lowMemory += OnLowMemory;
|
Application.lowMemory += OnLowMemory;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ namespace AlicizaX.Scene.Runtime
|
|||||||
{
|
{
|
||||||
if (!AppServices.TryGetApp<ISceneService>(out _))
|
if (!AppServices.TryGetApp<ISceneService>(out _))
|
||||||
{
|
{
|
||||||
AppServices.RegisterApp(new SceneService());
|
AppServices.RegisterApp<ISceneService>(new SceneService());
|
||||||
}
|
}
|
||||||
|
|
||||||
AppServices.EnsureScene();
|
AppServices.EnsureScene();
|
||||||
|
|||||||
@ -292,7 +292,7 @@ namespace AlicizaX.Scene.Runtime
|
|||||||
var sceneScope = Context.EnsureScene();
|
var sceneScope = Context.EnsureScene();
|
||||||
if (!sceneScope.TryGet<SceneDomainStateService>(out var sceneState))
|
if (!sceneScope.TryGet<SceneDomainStateService>(out var sceneState))
|
||||||
{
|
{
|
||||||
sceneState = sceneScope.Register(new SceneDomainStateService());
|
sceneState = (SceneDomainStateService)sceneScope.Register<ISceneStateService>(new SceneDomainStateService());
|
||||||
}
|
}
|
||||||
|
|
||||||
return sceneState;
|
return sceneState;
|
||||||
@ -301,7 +301,7 @@ namespace AlicizaX.Scene.Runtime
|
|||||||
private SceneDomainStateService PrepareSceneStateForMainSceneLoad(string location)
|
private SceneDomainStateService PrepareSceneStateForMainSceneLoad(string location)
|
||||||
{
|
{
|
||||||
var sceneScope = Context.ResetScene();
|
var sceneScope = Context.ResetScene();
|
||||||
var sceneState = sceneScope.Register(new SceneDomainStateService());
|
var sceneState = (SceneDomainStateService)sceneScope.Register<ISceneStateService>(new SceneDomainStateService());
|
||||||
sceneState.MarkMainSceneLoading(location);
|
sceneState.MarkMainSceneLoading(location);
|
||||||
return sceneState;
|
return sceneState;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,11 +29,27 @@ namespace AlicizaX.Timer.Runtime
|
|||||||
float GetLeftTime(ulong timerHandle);
|
float GetLeftTime(ulong timerHandle);
|
||||||
void Restart(ulong timerHandle);
|
void Restart(ulong timerHandle);
|
||||||
void RemoveTimer(ulong timerHandle);
|
void RemoveTimer(ulong timerHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnityEngine.Scripting.Preserve]
|
||||||
|
public interface ITimerCapacityService
|
||||||
|
{
|
||||||
|
void Prewarm(int capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnityEngine.Scripting.Preserve]
|
||||||
|
public interface ITimerDebugService
|
||||||
|
{
|
||||||
int GetAllTimers(TimerDebugInfo[] results);
|
int GetAllTimers(TimerDebugInfo[] results);
|
||||||
|
|
||||||
void GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
|
void GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
[UnityEngine.Scripting.Preserve]
|
||||||
|
public interface ITimerEditorDebugService
|
||||||
|
{
|
||||||
int GetStaleOneShotTimers(TimerDebugInfo[] results);
|
int GetStaleOneShotTimers(TimerDebugInfo[] results);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,12 @@ namespace AlicizaX.Timer.Runtime
|
|||||||
[UnityEngine.Scripting.Preserve]
|
[UnityEngine.Scripting.Preserve]
|
||||||
public sealed class TimerComponent : MonoBehaviour
|
public sealed class TimerComponent : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
private const int MIN_INITIAL_CAPACITY = 256;
|
||||||
|
|
||||||
|
[SerializeField]
|
||||||
|
[Min(MIN_INITIAL_CAPACITY)]
|
||||||
|
private int _initialCapacity = 1024;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
if (AppServices.TryGet<ITimerService>(out _))
|
if (AppServices.TryGet<ITimerService>(out _))
|
||||||
@ -15,7 +21,15 @@ namespace AlicizaX.Timer.Runtime
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppServices.RegisterApp(new TimerService());
|
AppServices.RegisterApp<ITimerService>(new TimerService(_initialCapacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnValidate()
|
||||||
|
{
|
||||||
|
if (_initialCapacity < MIN_INITIAL_CAPACITY)
|
||||||
|
{
|
||||||
|
_initialCapacity = MIN_INITIAL_CAPACITY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using Cysharp.Text;
|
||||||
|
|
||||||
public static class InstanceFactory
|
public static class InstanceFactory
|
||||||
{
|
{
|
||||||
@ -10,19 +11,19 @@ public static class InstanceFactory
|
|||||||
{
|
{
|
||||||
if (!_constructorCache.TryGetValue(type, out var constructor))
|
if (!_constructorCache.TryGetValue(type, out var constructor))
|
||||||
{
|
{
|
||||||
// 验证是否存在公共无参构造函数
|
// 楠岃瘉鏄惁瀛樺湪鍏叡鏃犲弬鏋勯€犲嚱鏁?
|
||||||
var ctor = type.GetConstructor(Type.EmptyTypes);
|
var ctor = type.GetConstructor(Type.EmptyTypes);
|
||||||
if (ctor == null)
|
if (ctor == null)
|
||||||
{
|
{
|
||||||
throw new MissingMethodException($"类型 {type.Name} 缺少公共无参构造函数");
|
throw new MissingMethodException(ZString.Format("Type {0} missing public parameterless constructor", type.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建表达式树:new T()
|
// 鏋勫缓琛ㄨ揪寮忔爲锛歯ew T()
|
||||||
var newExpr = Expression.New(ctor);
|
var newExpr = Expression.New(ctor);
|
||||||
var lambda = Expression.Lambda<Func<object>>(newExpr);
|
var lambda = Expression.Lambda<Func<object>>(newExpr);
|
||||||
constructor = lambda.Compile();
|
constructor = lambda.Compile();
|
||||||
|
|
||||||
// 缓存委托
|
// 缂撳瓨濮旀墭
|
||||||
_constructorCache[type] = constructor;
|
_constructorCache[type] = constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using AlicizaX.Resource.Runtime;
|
using AlicizaX.Resource.Runtime;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
|
using Cysharp.Text;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
@ -10,7 +11,6 @@ namespace AlicizaX.UI.Runtime
|
|||||||
public static class UIHolderFactory
|
public static class UIHolderFactory
|
||||||
{
|
{
|
||||||
private static IResourceService ResourceService => AppServices.Require<IResourceService>();
|
private static IResourceService ResourceService => AppServices.Require<IResourceService>();
|
||||||
private static bool AllowLegacyResourcesFallback => UnityEngine.Application.isEditor || UnityEngine.Debug.isDebugBuild;
|
|
||||||
|
|
||||||
public static async UniTask<T> CreateUIHolderAsync<T>(Transform parent) where T : UIHolderObjectBase
|
public static async UniTask<T> CreateUIHolderAsync<T>(Transform parent) where T : UIHolderObjectBase
|
||||||
{
|
{
|
||||||
@ -90,65 +90,23 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
private static async UniTask<GameObject> LoadResourceWithFallbackAsync(string location, Transform parent)
|
private static async UniTask<GameObject> 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);
|
return await InstantiateResourceAsync(location, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameObject LoadResourceWithFallbackSync(string location, Transform 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);
|
return InstantiateResourceSync(location, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateAndBind(UIMetadata meta, GameObject holderObject, UIBase owner)
|
private static void ValidateAndBind(UIMetadata meta, GameObject holderObject, UIBase owner)
|
||||||
{
|
{
|
||||||
if (!holderObject) throw new NullReferenceException($"UI resource load failed: {meta.ResInfo.Location}");
|
if (!holderObject) throw new NullReferenceException(ZString.Format("UI resource load failed: {0}", meta.ResInfo.Location));
|
||||||
|
|
||||||
var holder = (UIHolderObjectBase)holderObject.GetComponent(meta.View.UIHolderType);
|
var holder = (UIHolderObjectBase)holderObject.GetComponent(meta.View.UIHolderType);
|
||||||
|
|
||||||
if (holder == null)
|
if (holder == null)
|
||||||
{
|
{
|
||||||
throw new InvalidCastException($"资源{holderObject.name}上不存在{meta.View.UIHolderType.FullName}");
|
throw new InvalidCastException(ZString.Format("UI resource {0} missing holder component {1}", holderObject.name, meta.View.UIHolderType.FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.View?.BindUIHolder(holder, owner);
|
meta.View?.BindUIHolder(holder, owner);
|
||||||
|
|||||||
@ -15,8 +15,9 @@ namespace AlicizaX.UI.Runtime
|
|||||||
public readonly bool FullScreen;
|
public readonly bool FullScreen;
|
||||||
public readonly int CacheTime;
|
public readonly int CacheTime;
|
||||||
public readonly bool NeedUpdate;
|
public readonly bool NeedUpdate;
|
||||||
|
public readonly int TypeId;
|
||||||
|
|
||||||
public UIMetaInfo(RuntimeTypeHandle runtimeTypeHandle, RuntimeTypeHandle holderRuntimeTypeHandle, UILayer windowLayer, bool fullScreen, int cacheTime, bool needUpdate)
|
public UIMetaInfo(RuntimeTypeHandle runtimeTypeHandle, RuntimeTypeHandle holderRuntimeTypeHandle, UILayer windowLayer, bool fullScreen, int cacheTime, bool needUpdate, int typeId)
|
||||||
{
|
{
|
||||||
RuntimeTypeHandle = runtimeTypeHandle;
|
RuntimeTypeHandle = runtimeTypeHandle;
|
||||||
HolderRuntimeTypeHandle = holderRuntimeTypeHandle;
|
HolderRuntimeTypeHandle = holderRuntimeTypeHandle;
|
||||||
@ -24,18 +25,22 @@ namespace AlicizaX.UI.Runtime
|
|||||||
FullScreen = fullScreen;
|
FullScreen = fullScreen;
|
||||||
CacheTime = cacheTime;
|
CacheTime = cacheTime;
|
||||||
NeedUpdate = needUpdate;
|
NeedUpdate = needUpdate;
|
||||||
|
TypeId = typeId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<RuntimeTypeHandle, UIMetaInfo> _typeHandleMap = new();
|
private static readonly Dictionary<RuntimeTypeHandle, UIMetaInfo> _typeHandleMap = new();
|
||||||
private static readonly Dictionary<string, RuntimeTypeHandle> _stringHandleMap = new();
|
private static readonly Dictionary<string, RuntimeTypeHandle> _stringHandleMap = new();
|
||||||
|
private static int _nextTypeId;
|
||||||
|
public static int TypeCount => _nextTypeId;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Register(Type uiType, Type holderType, UILayer layer = UILayer.UI, bool fullScreen = false, int cacheTime = 0, bool needUpdate = false)
|
public static void Register(Type uiType, Type holderType, UILayer layer = UILayer.UI, bool fullScreen = false, int cacheTime = 0, bool needUpdate = false)
|
||||||
{
|
{
|
||||||
RuntimeTypeHandle holderHandle = holderType.TypeHandle;
|
RuntimeTypeHandle holderHandle = holderType.TypeHandle;
|
||||||
RuntimeTypeHandle uiHandle = uiType.TypeHandle;
|
RuntimeTypeHandle uiHandle = uiType.TypeHandle;
|
||||||
_typeHandleMap[uiHandle] = new UIMetaInfo(uiHandle, holderHandle, layer, fullScreen, cacheTime, needUpdate);
|
int typeId = _typeHandleMap.TryGetValue(uiHandle, out UIMetaInfo oldInfo) ? oldInfo.TypeId : _nextTypeId++;
|
||||||
|
_typeHandleMap[uiHandle] = new UIMetaInfo(uiHandle, holderHandle, layer, fullScreen, cacheTime, needUpdate, typeId);
|
||||||
_stringHandleMap[uiType.Name] = uiHandle;
|
_stringHandleMap[uiType.Name] = uiHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
|
using Cysharp.Text;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
|
|
||||||
namespace AlicizaX.UI.Runtime
|
namespace AlicizaX.UI.Runtime
|
||||||
@ -12,10 +13,21 @@ namespace AlicizaX.UI.Runtime
|
|||||||
public readonly UIMetaRegistry.UIMetaInfo MetaInfo;
|
public readonly UIMetaRegistry.UIMetaInfo MetaInfo;
|
||||||
public readonly UIResRegistry.UIResInfo ResInfo;
|
public readonly UIResRegistry.UIResInfo ResInfo;
|
||||||
public readonly Type UILogicType;
|
public readonly Type UILogicType;
|
||||||
|
public readonly string UILogicTypeName;
|
||||||
|
public readonly string UIHolderTypeName;
|
||||||
public bool InCache = false;
|
public bool InCache = false;
|
||||||
|
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
public CancellationToken CancellationToken => _cancellationTokenSource?.Token ?? CancellationToken.None;
|
public CancellationToken CancellationToken => _cancellationTokenSource?.Token ?? CancellationToken.None;
|
||||||
|
private int _operationVersion;
|
||||||
|
private bool _cancelRequested;
|
||||||
|
private bool _showInProgress;
|
||||||
|
private bool _closeInProgress;
|
||||||
|
|
||||||
|
public int OperationVersion => _operationVersion;
|
||||||
|
public bool CancelRequested => _cancelRequested;
|
||||||
|
public bool ShowInProgress => _showInProgress;
|
||||||
|
public bool CloseInProgress => _closeInProgress;
|
||||||
|
|
||||||
public UIState State
|
public UIState State
|
||||||
{
|
{
|
||||||
@ -48,8 +60,64 @@ namespace AlicizaX.UI.Runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool BeginShowOperation()
|
||||||
|
{
|
||||||
|
if (_showInProgress)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestCancelCurrentOperation();
|
||||||
|
EnsureCancellationToken();
|
||||||
|
_operationVersion++;
|
||||||
|
_cancelRequested = false;
|
||||||
|
_showInProgress = true;
|
||||||
|
_closeInProgress = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BeginCloseOperation()
|
||||||
|
{
|
||||||
|
if (_closeInProgress)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestCancelCurrentOperation();
|
||||||
|
EnsureCancellationToken();
|
||||||
|
_operationVersion++;
|
||||||
|
_cancelRequested = false;
|
||||||
|
_closeInProgress = true;
|
||||||
|
_showInProgress = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndShowOperation(int operationVersion)
|
||||||
|
{
|
||||||
|
if (_operationVersion == operationVersion)
|
||||||
|
{
|
||||||
|
_showInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndCloseOperation(int operationVersion)
|
||||||
|
{
|
||||||
|
if (_operationVersion == operationVersion)
|
||||||
|
{
|
||||||
|
_closeInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void CancelAsyncOperations()
|
public void CancelAsyncOperations()
|
||||||
{
|
{
|
||||||
|
RequestCancelCurrentOperation();
|
||||||
|
_showInProgress = false;
|
||||||
|
_closeInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestCancelCurrentOperation()
|
||||||
|
{
|
||||||
|
_cancelRequested = true;
|
||||||
_cancellationTokenSource?.Cancel();
|
_cancellationTokenSource?.Cancel();
|
||||||
_cancellationTokenSource?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
_cancellationTokenSource = null;
|
_cancellationTokenSource = null;
|
||||||
@ -90,16 +158,19 @@ namespace AlicizaX.UI.Runtime
|
|||||||
}
|
}
|
||||||
|
|
||||||
UILogicType = uiType;
|
UILogicType = uiType;
|
||||||
|
UILogicTypeName = uiType.Name;
|
||||||
|
|
||||||
if (!UIMetaRegistry.TryGet(UILogicType.TypeHandle, out MetaInfo))
|
if (!UIMetaRegistry.TryGet(UILogicType.TypeHandle, out MetaInfo))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"[UI] Metadata not registered for {UILogicType.FullName}");
|
throw new InvalidOperationException(ZString.Format("[UI] Metadata not registered for {0}", UILogicType.FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!UIResRegistry.TryGet(MetaInfo.HolderRuntimeTypeHandle, out ResInfo))
|
if (!UIResRegistry.TryGet(MetaInfo.HolderRuntimeTypeHandle, out ResInfo))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"[UI] Resource metadata not registered for holder of {UILogicType.FullName}");
|
throw new InvalidOperationException(ZString.Format("[UI] Resource metadata not registered for holder of {0}", UILogicType.FullName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UIHolderTypeName = Type.GetTypeFromHandle(MetaInfo.HolderRuntimeTypeHandle)?.Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,14 +12,6 @@ namespace AlicizaX.UI.Runtime
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UIMetadataObject Create(UIMetadata target, RuntimeTypeHandle handle)
|
|
||||||
{
|
|
||||||
UIMetadataObject obj = MemoryPool.Acquire<UIMetadataObject>();
|
|
||||||
obj.Initialize(handle.GetHashCode().ToString(), target);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected internal override void Release(bool isShutdown)
|
protected internal override void Release(bool isShutdown)
|
||||||
{
|
{
|
||||||
UIMetadata metadata = Target;
|
UIMetadata metadata = Target;
|
||||||
|
|||||||
105
Runtime/UI/Manager/UIDebugInfo.cs
Normal file
105
Runtime/UI/Manager/UIDebugInfo.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.Runtime
|
||||||
|
{
|
||||||
|
internal interface IUIDebugService
|
||||||
|
{
|
||||||
|
int LayerCount { get; }
|
||||||
|
int CacheWindowCount { get; }
|
||||||
|
void FillServiceDebugInfo(UIServiceDebugInfo info);
|
||||||
|
bool FillLayerDebugInfo(int layerIndex, UILayerDebugInfo info);
|
||||||
|
bool FillWindowDebugInfo(int layerIndex, int windowIndex, UIWindowDebugInfo info);
|
||||||
|
int FillCacheDebugInfo(UIWindowDebugInfo[] infos, int capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UIServiceDebugInfo
|
||||||
|
{
|
||||||
|
public bool Initialized;
|
||||||
|
public bool Orthographic;
|
||||||
|
public int LayerCount;
|
||||||
|
public int OpenWindowCount;
|
||||||
|
public int CacheWindowCount;
|
||||||
|
public int UpdateWindowCount;
|
||||||
|
public ulong BlockTimerHandle;
|
||||||
|
public bool BlockActive;
|
||||||
|
public Camera Camera;
|
||||||
|
public Canvas Canvas;
|
||||||
|
public Transform Root;
|
||||||
|
public Transform CanvasRoot;
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Initialized = false;
|
||||||
|
Orthographic = false;
|
||||||
|
LayerCount = 0;
|
||||||
|
OpenWindowCount = 0;
|
||||||
|
CacheWindowCount = 0;
|
||||||
|
UpdateWindowCount = 0;
|
||||||
|
BlockTimerHandle = 0UL;
|
||||||
|
BlockActive = false;
|
||||||
|
Camera = null;
|
||||||
|
Canvas = null;
|
||||||
|
Root = null;
|
||||||
|
CanvasRoot = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UILayerDebugInfo
|
||||||
|
{
|
||||||
|
public int LayerIndex;
|
||||||
|
public UILayer Layer;
|
||||||
|
public int WindowCount;
|
||||||
|
public int LastFullscreenIndex;
|
||||||
|
public RectTransform RectTransform;
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
LayerIndex = 0;
|
||||||
|
Layer = UILayer.Background;
|
||||||
|
WindowCount = 0;
|
||||||
|
LastFullscreenIndex = -1;
|
||||||
|
RectTransform = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class UIWindowDebugInfo
|
||||||
|
{
|
||||||
|
public int LayerIndex;
|
||||||
|
public int OrderIndex;
|
||||||
|
public RuntimeTypeHandle RuntimeTypeHandle;
|
||||||
|
public string LogicTypeName;
|
||||||
|
public string HolderTypeName;
|
||||||
|
public UIState State;
|
||||||
|
public bool Visible;
|
||||||
|
public bool FullScreen;
|
||||||
|
public bool InCache;
|
||||||
|
public bool NeedUpdate;
|
||||||
|
public bool ShowInProgress;
|
||||||
|
public bool CloseInProgress;
|
||||||
|
public int Depth;
|
||||||
|
public float CacheTime;
|
||||||
|
public ulong CacheTimerHandle;
|
||||||
|
public Transform HolderTransform;
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
LayerIndex = 0;
|
||||||
|
OrderIndex = 0;
|
||||||
|
RuntimeTypeHandle = default;
|
||||||
|
LogicTypeName = null;
|
||||||
|
HolderTypeName = null;
|
||||||
|
State = UIState.Uninitialized;
|
||||||
|
Visible = false;
|
||||||
|
FullScreen = false;
|
||||||
|
InCache = false;
|
||||||
|
NeedUpdate = false;
|
||||||
|
ShowInProgress = false;
|
||||||
|
CloseInProgress = false;
|
||||||
|
Depth = 0;
|
||||||
|
CacheTime = 0f;
|
||||||
|
CacheTimerHandle = 0UL;
|
||||||
|
HolderTransform = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Runtime/UI/Manager/UIDebugInfo.cs.meta
Normal file
11
Runtime/UI/Manager/UIDebugInfo.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bdaac10881036e041859876d3852c452
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
using AlicizaX.Timer.Runtime;
|
using AlicizaX.Timer.Runtime;
|
||||||
|
using Cysharp.Text;
|
||||||
|
|
||||||
namespace AlicizaX.UI.Runtime
|
namespace AlicizaX.UI.Runtime
|
||||||
{
|
{
|
||||||
@ -19,7 +19,9 @@ namespace AlicizaX.UI.Runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<RuntimeTypeHandle, CacheEntry> m_CacheWindow = new();
|
private CacheEntry[] m_CacheWindow = new CacheEntry[8];
|
||||||
|
private int[] m_CacheTypeIdToIndex = CreateCacheIndexArray(8);
|
||||||
|
private int m_CacheWindowCount;
|
||||||
|
|
||||||
private void CacheWindow(UIMetadata uiMetadata, bool force)
|
private void CacheWindow(UIMetadata uiMetadata, bool force)
|
||||||
{
|
{
|
||||||
@ -35,7 +37,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveFromCache(uiMetadata.MetaInfo.RuntimeTypeHandle);
|
RemoveFromCache(uiMetadata.MetaInfo.TypeId);
|
||||||
ulong timerHandle = 0UL;
|
ulong timerHandle = 0UL;
|
||||||
|
|
||||||
uiMetadata.View.Holder.transform.SetParent(UICacheLayer);
|
uiMetadata.View.Holder.transform.SetParent(UICacheLayer);
|
||||||
@ -51,36 +53,115 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
if (timerHandle == 0UL)
|
if (timerHandle == 0UL)
|
||||||
{
|
{
|
||||||
Log.Warning($"Failed to create cache timer for {uiMetadata.UILogicType.Name}");
|
Log.Warning(ZString.Format("Failed to create cache timer for {0}", uiMetadata.UILogicType.Name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uiMetadata.InCache = true;
|
uiMetadata.InCache = true;
|
||||||
m_CacheWindow.Add(uiMetadata.MetaInfo.RuntimeTypeHandle, new CacheEntry(uiMetadata, timerHandle));
|
AddToCache(uiMetadata, timerHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTimerDisposeWindow(UIMetadata meta)
|
private void OnTimerDisposeWindow(UIMetadata meta)
|
||||||
{
|
{
|
||||||
if (meta != null)
|
if (meta != null)
|
||||||
{
|
{
|
||||||
RemoveFromCache(meta.MetaInfo.RuntimeTypeHandle);
|
RemoveFromCache(meta.MetaInfo.TypeId);
|
||||||
meta.DisposeImmediate();
|
meta.DisposeImmediate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveFromCache(RuntimeTypeHandle typeHandle)
|
private void RemoveFromCache(RuntimeTypeHandle typeHandle)
|
||||||
{
|
{
|
||||||
if (m_CacheWindow.TryGetValue(typeHandle, out var entry))
|
if (UIMetaRegistry.TryGet(typeHandle, out UIMetaRegistry.UIMetaInfo metaInfo))
|
||||||
{
|
{
|
||||||
m_CacheWindow.Remove(typeHandle);
|
RemoveFromCache(metaInfo.TypeId);
|
||||||
entry.Metadata.InCache = false;
|
|
||||||
if (entry.TimerHandle != 0UL && _timerService != null)
|
|
||||||
{
|
|
||||||
_timerService.RemoveTimer(entry.TimerHandle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RemoveFromCache(int typeId)
|
||||||
|
{
|
||||||
|
if ((uint)typeId >= (uint)m_CacheTypeIdToIndex.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = m_CacheTypeIdToIndex[typeId];
|
||||||
|
if (index < 0 || index >= m_CacheWindowCount)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheEntry entry = m_CacheWindow[index];
|
||||||
|
int lastIndex = m_CacheWindowCount - 1;
|
||||||
|
CacheEntry last = m_CacheWindow[lastIndex];
|
||||||
|
m_CacheWindow[index] = last;
|
||||||
|
m_CacheWindow[lastIndex] = default;
|
||||||
|
m_CacheWindowCount = lastIndex;
|
||||||
|
m_CacheTypeIdToIndex[typeId] = -1;
|
||||||
|
if (index != lastIndex && last.Metadata != null)
|
||||||
|
{
|
||||||
|
m_CacheTypeIdToIndex[last.Metadata.MetaInfo.TypeId] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Metadata.InCache = false;
|
||||||
|
if (entry.TimerHandle != 0UL && _timerService != null)
|
||||||
|
{
|
||||||
|
_timerService.RemoveTimer(entry.TimerHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToCache(UIMetadata metadata, ulong timerHandle)
|
||||||
|
{
|
||||||
|
int typeId = metadata.MetaInfo.TypeId;
|
||||||
|
EnsureCacheIndexCapacity(typeId);
|
||||||
|
EnsureCacheCapacity();
|
||||||
|
int index = m_CacheWindowCount++;
|
||||||
|
m_CacheWindow[index] = new CacheEntry(metadata, timerHandle);
|
||||||
|
m_CacheTypeIdToIndex[typeId] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCacheCapacity()
|
||||||
|
{
|
||||||
|
if (m_CacheWindowCount < m_CacheWindow.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Resize(ref m_CacheWindow, m_CacheWindow.Length << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCacheIndexCapacity(int typeId)
|
||||||
|
{
|
||||||
|
if ((uint)typeId < (uint)m_CacheTypeIdToIndex.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oldLength = m_CacheTypeIdToIndex.Length;
|
||||||
|
int newLength = oldLength;
|
||||||
|
while (newLength <= typeId)
|
||||||
|
{
|
||||||
|
newLength <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Resize(ref m_CacheTypeIdToIndex, newLength);
|
||||||
|
for (int i = oldLength; i < newLength; i++)
|
||||||
|
{
|
||||||
|
m_CacheTypeIdToIndex[i] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] CreateCacheIndexArray(int capacity)
|
||||||
|
{
|
||||||
|
int[] values = new int[capacity];
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
values[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
private ITimerService GetTimerService()
|
private ITimerService GetTimerService()
|
||||||
{
|
{
|
||||||
if (_timerService != null)
|
if (_timerService != null)
|
||||||
|
|||||||
149
Runtime/UI/Manager/UIService.Debug.cs
Normal file
149
Runtime/UI/Manager/UIService.Debug.cs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
namespace AlicizaX.UI.Runtime
|
||||||
|
{
|
||||||
|
internal sealed partial class UIService
|
||||||
|
{
|
||||||
|
int IUIDebugService.LayerCount => _openUI.Length;
|
||||||
|
int IUIDebugService.CacheWindowCount => m_CacheWindowCount;
|
||||||
|
|
||||||
|
void IUIDebugService.FillServiceDebugInfo(UIServiceDebugInfo info)
|
||||||
|
{
|
||||||
|
if (info == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int openWindowCount = 0;
|
||||||
|
int updateWindowCount = 0;
|
||||||
|
for (int i = 0; i < _openUI.Length; i++)
|
||||||
|
{
|
||||||
|
LayerData layer = _openUI[i];
|
||||||
|
if (layer == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = layer.Count;
|
||||||
|
openWindowCount += count;
|
||||||
|
for (int j = 0; j < count; j++)
|
||||||
|
{
|
||||||
|
UIMetadata metadata = layer.Items[j];
|
||||||
|
if (metadata != null && metadata.MetaInfo.NeedUpdate)
|
||||||
|
{
|
||||||
|
updateWindowCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Initialized = UIRoot != null && UICanvas != null;
|
||||||
|
info.Orthographic = _isOrthographic;
|
||||||
|
info.LayerCount = _openUI.Length;
|
||||||
|
info.OpenWindowCount = openWindowCount;
|
||||||
|
info.CacheWindowCount = m_CacheWindowCount;
|
||||||
|
info.UpdateWindowCount = updateWindowCount;
|
||||||
|
info.BlockTimerHandle = m_LastCountDownHandle;
|
||||||
|
info.BlockActive = m_LayerBlock != null && m_LayerBlock.activeSelf;
|
||||||
|
info.Camera = UICamera;
|
||||||
|
info.Canvas = UICanvas;
|
||||||
|
info.Root = UIRoot;
|
||||||
|
info.CanvasRoot = UICanvasRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IUIDebugService.FillLayerDebugInfo(int layerIndex, UILayerDebugInfo info)
|
||||||
|
{
|
||||||
|
if (info == null || (uint)layerIndex >= (uint)_openUI.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayerData layer = _openUI[layerIndex];
|
||||||
|
if (layer == null)
|
||||||
|
{
|
||||||
|
info.Clear();
|
||||||
|
info.LayerIndex = layerIndex;
|
||||||
|
info.Layer = (UILayer)layerIndex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.LayerIndex = layerIndex;
|
||||||
|
info.Layer = (UILayer)layerIndex;
|
||||||
|
info.WindowCount = layer.Count;
|
||||||
|
info.LastFullscreenIndex = layer.LastFullscreenIndex;
|
||||||
|
info.RectTransform = m_AllWindowLayer[layerIndex];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IUIDebugService.FillWindowDebugInfo(int layerIndex, int windowIndex, UIWindowDebugInfo info)
|
||||||
|
{
|
||||||
|
if (info == null || (uint)layerIndex >= (uint)_openUI.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayerData layer = _openUI[layerIndex];
|
||||||
|
if (layer == null || (uint)windowIndex >= (uint)layer.Count)
|
||||||
|
{
|
||||||
|
info?.Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FillWindowDebugInfo(layer.Items[windowIndex], layerIndex, windowIndex, 0UL, info);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int IUIDebugService.FillCacheDebugInfo(UIWindowDebugInfo[] infos, int capacity)
|
||||||
|
{
|
||||||
|
if (infos == null || capacity <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (int i = 0; i < m_CacheWindowCount; i++)
|
||||||
|
{
|
||||||
|
if (index >= capacity || index >= infos.Length)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIWindowDebugInfo info = infos[index];
|
||||||
|
if (info != null)
|
||||||
|
{
|
||||||
|
CacheEntry entry = m_CacheWindow[i];
|
||||||
|
FillWindowDebugInfo(entry.Metadata, entry.Metadata.MetaInfo.UILayer, index, entry.TimerHandle, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillWindowDebugInfo(UIMetadata metadata, int layerIndex, int orderIndex, ulong timerHandle, UIWindowDebugInfo info)
|
||||||
|
{
|
||||||
|
if (metadata == null)
|
||||||
|
{
|
||||||
|
info.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIBase view = metadata.View;
|
||||||
|
UIHolderObjectBase holder = view?.Holder;
|
||||||
|
info.LayerIndex = layerIndex;
|
||||||
|
info.OrderIndex = orderIndex;
|
||||||
|
info.RuntimeTypeHandle = metadata.MetaInfo.RuntimeTypeHandle;
|
||||||
|
info.LogicTypeName = metadata.UILogicTypeName;
|
||||||
|
info.HolderTypeName = metadata.UIHolderTypeName;
|
||||||
|
info.State = metadata.State;
|
||||||
|
info.Visible = view != null && view.Visible;
|
||||||
|
info.FullScreen = metadata.MetaInfo.FullScreen;
|
||||||
|
info.InCache = metadata.InCache;
|
||||||
|
info.NeedUpdate = metadata.MetaInfo.NeedUpdate;
|
||||||
|
info.ShowInProgress = metadata.ShowInProgress;
|
||||||
|
info.CloseInProgress = metadata.CloseInProgress;
|
||||||
|
info.Depth = view != null ? view.Depth : 0;
|
||||||
|
info.CacheTime = metadata.MetaInfo.CacheTime;
|
||||||
|
info.CacheTimerHandle = timerHandle;
|
||||||
|
info.HolderTransform = holder != null ? holder.transform : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Runtime/UI/Manager/UIService.Debug.cs.meta
Normal file
11
Runtime/UI/Manager/UIService.Debug.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7e3f116e63c64ff4d82fa01efb769894
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using Cysharp.Text;
|
||||||
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace AlicizaX.UI.Runtime
|
namespace AlicizaX.UI.Runtime
|
||||||
@ -57,7 +58,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
private void AddLayer(int layer)
|
private void AddLayer(int layer)
|
||||||
{
|
{
|
||||||
var layerObject = new GameObject($"Layer{layer}-{(UILayer)layer}");
|
var layerObject = new GameObject(ZString.Format("Layer{0}-{1}", layer, (UILayer)layer));
|
||||||
var rect = layerObject.AddComponent<RectTransform>();
|
var rect = layerObject.AddComponent<RectTransform>();
|
||||||
rect.SetParent(UICanvasRoot);
|
rect.SetParent(UICanvasRoot);
|
||||||
rect.localScale = Vector3.one;
|
rect.localScale = Vector3.one;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
@ -9,16 +8,54 @@ namespace AlicizaX.UI.Runtime
|
|||||||
{
|
{
|
||||||
sealed class LayerData
|
sealed class LayerData
|
||||||
{
|
{
|
||||||
public readonly List<UIMetadata> OrderList;
|
public UIMetadata[] Items;
|
||||||
public readonly Dictionary<RuntimeTypeHandle, int> IndexMap;
|
public int[] TypeIdToIndex;
|
||||||
|
public int Count;
|
||||||
public int LastFullscreenIndex;
|
public int LastFullscreenIndex;
|
||||||
|
|
||||||
public LayerData(int initialCapacity)
|
public LayerData(int initialCapacity)
|
||||||
{
|
{
|
||||||
OrderList = new List<UIMetadata>(initialCapacity);
|
Items = new UIMetadata[initialCapacity];
|
||||||
IndexMap = new Dictionary<RuntimeTypeHandle, int>(initialCapacity);
|
TypeIdToIndex = new int[initialCapacity];
|
||||||
|
for (int i = 0; i < TypeIdToIndex.Length; i++)
|
||||||
|
{
|
||||||
|
TypeIdToIndex[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Count = 0;
|
||||||
LastFullscreenIndex = -1;
|
LastFullscreenIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void EnsureTypeCapacity(int typeId)
|
||||||
|
{
|
||||||
|
if ((uint)typeId < (uint)TypeIdToIndex.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oldLength = TypeIdToIndex.Length;
|
||||||
|
int newLength = oldLength;
|
||||||
|
while (newLength <= typeId)
|
||||||
|
{
|
||||||
|
newLength <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Resize(ref TypeIdToIndex, newLength);
|
||||||
|
for (int i = oldLength; i < newLength; i++)
|
||||||
|
{
|
||||||
|
TypeIdToIndex[i] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnsureItemCapacity()
|
||||||
|
{
|
||||||
|
if (Count < Items.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Resize(ref Items, Items.Length << 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed partial class UIService
|
internal sealed partial class UIService
|
||||||
@ -28,30 +65,51 @@ namespace AlicizaX.UI.Runtime
|
|||||||
private async UniTask<UIBase> ShowUIImplAsync(UIMetadata metaInfo, params object[] userDatas)
|
private async UniTask<UIBase> ShowUIImplAsync(UIMetadata metaInfo, params object[] userDatas)
|
||||||
{
|
{
|
||||||
CreateMetaUI(metaInfo);
|
CreateMetaUI(metaInfo);
|
||||||
EnsureMetaCanOpen(metaInfo);
|
if (!metaInfo.BeginShowOperation())
|
||||||
await UIHolderFactory.CreateUIResourceAsync(metaInfo, UICacheLayer);
|
|
||||||
if (metaInfo.View == null || metaInfo.State == UIState.Uninitialized || metaInfo.State == UIState.Destroyed)
|
|
||||||
{
|
{
|
||||||
|
metaInfo.View?.RefreshParams(userDatas);
|
||||||
|
return metaInfo.View;
|
||||||
|
}
|
||||||
|
|
||||||
|
int operationVersion = metaInfo.OperationVersion;
|
||||||
|
await UIHolderFactory.CreateUIResourceAsync(metaInfo, UICacheLayer);
|
||||||
|
if (operationVersion != metaInfo.OperationVersion || metaInfo.View == null || metaInfo.State == UIState.Uninitialized || metaInfo.State == UIState.Destroyed)
|
||||||
|
{
|
||||||
|
metaInfo.EndShowOperation(operationVersion);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
FinalizeShow(metaInfo, userDatas);
|
FinalizeShow(metaInfo, userDatas);
|
||||||
await UpdateVisualState(metaInfo, metaInfo.CancellationToken);
|
bool showResult = await UpdateVisualState(metaInfo, metaInfo.CancellationToken);
|
||||||
return metaInfo.View;
|
metaInfo.EndShowOperation(operationVersion);
|
||||||
|
return showResult ? metaInfo.View : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UIBase ShowUIImplSync(UIMetadata metaInfo, params object[] userDatas)
|
private UIBase ShowUIImplSync(UIMetadata metaInfo, params object[] userDatas)
|
||||||
{
|
{
|
||||||
CreateMetaUI(metaInfo);
|
CreateMetaUI(metaInfo);
|
||||||
EnsureMetaCanOpen(metaInfo);
|
if (!metaInfo.BeginShowOperation())
|
||||||
UIHolderFactory.CreateUIResourceSync(metaInfo, UICacheLayer);
|
|
||||||
if (metaInfo.View == null || metaInfo.State == UIState.Uninitialized || metaInfo.State == UIState.Destroyed)
|
|
||||||
{
|
{
|
||||||
|
metaInfo.View?.RefreshParams(userDatas);
|
||||||
|
return metaInfo.View;
|
||||||
|
}
|
||||||
|
|
||||||
|
int operationVersion = metaInfo.OperationVersion;
|
||||||
|
UIHolderFactory.CreateUIResourceSync(metaInfo, UICacheLayer);
|
||||||
|
if (operationVersion != metaInfo.OperationVersion || metaInfo.View == null || metaInfo.State == UIState.Uninitialized || metaInfo.State == UIState.Destroyed)
|
||||||
|
{
|
||||||
|
metaInfo.EndShowOperation(operationVersion);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
FinalizeShow(metaInfo, userDatas);
|
FinalizeShow(metaInfo, userDatas);
|
||||||
UpdateVisualState(metaInfo).Forget();
|
bool showResult = UpdateVisualStateSync(metaInfo);
|
||||||
|
metaInfo.EndShowOperation(operationVersion);
|
||||||
|
if (!showResult)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return metaInfo.View;
|
return metaInfo.View;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,29 +120,35 @@ namespace AlicizaX.UI.Runtime
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!meta.BeginCloseOperation())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int operationVersion = meta.OperationVersion;
|
||||||
|
|
||||||
if (meta.State == UIState.CreatedUI)
|
if (meta.State == UIState.CreatedUI)
|
||||||
{
|
{
|
||||||
meta.CancelAsyncOperations();
|
|
||||||
await meta.DisposeAsync();
|
await meta.DisposeAsync();
|
||||||
|
meta.EndCloseOperation(operationVersion);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta.State == UIState.Loaded || meta.State == UIState.Initialized)
|
if (meta.State == UIState.Loaded || meta.State == UIState.Initialized)
|
||||||
{
|
{
|
||||||
meta.CancelAsyncOperations();
|
|
||||||
var popResult = Pop(meta);
|
var popResult = Pop(meta);
|
||||||
SortWindowVisible(meta.MetaInfo.UILayer, popResult.previousFullscreenIndex);
|
SortWindowVisible(meta.MetaInfo.UILayer, popResult.previousFullscreenIndex);
|
||||||
SortWindowDepth(meta.MetaInfo.UILayer, popResult.removedIndex >= 0 ? popResult.removedIndex : 0);
|
SortWindowDepth(meta.MetaInfo.UILayer, popResult.removedIndex >= 0 ? popResult.removedIndex : 0);
|
||||||
meta.View.Visible = false;
|
meta.View.Visible = false;
|
||||||
CacheWindow(meta, force);
|
CacheWindow(meta, force);
|
||||||
|
meta.EndCloseOperation(operationVersion);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.CancelAsyncOperations();
|
bool closeResult = await meta.View.InternalClose(meta.CancellationToken);
|
||||||
meta.EnsureCancellationToken();
|
if (!closeResult || meta.State != UIState.Closed)
|
||||||
await meta.View.InternalClose();
|
|
||||||
if (meta.State != UIState.Closed)
|
|
||||||
{
|
{
|
||||||
|
meta.EndCloseOperation(operationVersion);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +156,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
SortWindowVisible(meta.MetaInfo.UILayer, closedPopResult.previousFullscreenIndex);
|
SortWindowVisible(meta.MetaInfo.UILayer, closedPopResult.previousFullscreenIndex);
|
||||||
SortWindowDepth(meta.MetaInfo.UILayer, closedPopResult.removedIndex >= 0 ? closedPopResult.removedIndex : 0);
|
SortWindowDepth(meta.MetaInfo.UILayer, closedPopResult.removedIndex >= 0 ? closedPopResult.removedIndex : 0);
|
||||||
CacheWindow(meta, force);
|
CacheWindow(meta, force);
|
||||||
|
meta.EndCloseOperation(operationVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -106,16 +171,6 @@ namespace AlicizaX.UI.Runtime
|
|||||||
if (meta.State == UIState.Uninitialized) meta.CreateUI();
|
if (meta.State == UIState.Uninitialized) meta.CreateUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static void EnsureMetaCanOpen(UIMetadata meta)
|
|
||||||
{
|
|
||||||
if (meta.State == UIState.Closed)
|
|
||||||
{
|
|
||||||
meta.EnsureCancellationToken();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void FinalizeShow(UIMetadata meta, object[] userDatas)
|
private void FinalizeShow(UIMetadata meta, object[] userDatas)
|
||||||
{
|
{
|
||||||
if (meta.InCache)
|
if (meta.InCache)
|
||||||
@ -145,11 +200,14 @@ namespace AlicizaX.UI.Runtime
|
|||||||
private void Push(UIMetadata meta)
|
private void Push(UIMetadata meta)
|
||||||
{
|
{
|
||||||
var layer = _openUI[meta.MetaInfo.UILayer];
|
var layer = _openUI[meta.MetaInfo.UILayer];
|
||||||
if (!layer.IndexMap.ContainsKey(meta.MetaInfo.RuntimeTypeHandle))
|
int typeId = meta.MetaInfo.TypeId;
|
||||||
|
layer.EnsureTypeCapacity(typeId);
|
||||||
|
if (layer.TypeIdToIndex[typeId] < 0)
|
||||||
{
|
{
|
||||||
int index = layer.OrderList.Count;
|
layer.EnsureItemCapacity();
|
||||||
layer.OrderList.Add(meta);
|
int index = layer.Count++;
|
||||||
layer.IndexMap[meta.MetaInfo.RuntimeTypeHandle] = index;
|
layer.Items[index] = meta;
|
||||||
|
layer.TypeIdToIndex[typeId] = index;
|
||||||
if (meta.MetaInfo.FullScreen)
|
if (meta.MetaInfo.FullScreen)
|
||||||
{
|
{
|
||||||
layer.LastFullscreenIndex = index;
|
layer.LastFullscreenIndex = index;
|
||||||
@ -164,17 +222,27 @@ namespace AlicizaX.UI.Runtime
|
|||||||
{
|
{
|
||||||
var layer = _openUI[meta.MetaInfo.UILayer];
|
var layer = _openUI[meta.MetaInfo.UILayer];
|
||||||
int previousFullscreenIndex = layer.LastFullscreenIndex;
|
int previousFullscreenIndex = layer.LastFullscreenIndex;
|
||||||
if (layer.IndexMap.TryGetValue(meta.MetaInfo.RuntimeTypeHandle, out int index))
|
int typeId = meta.MetaInfo.TypeId;
|
||||||
|
if ((uint)typeId < (uint)layer.TypeIdToIndex.Length)
|
||||||
{
|
{
|
||||||
layer.OrderList.RemoveAt(index);
|
int index = layer.TypeIdToIndex[typeId];
|
||||||
layer.IndexMap.Remove(meta.MetaInfo.RuntimeTypeHandle);
|
if (index < 0)
|
||||||
|
|
||||||
for (int i = index; i < layer.OrderList.Count; i++)
|
|
||||||
{
|
{
|
||||||
var item = layer.OrderList[i];
|
return (-1, previousFullscreenIndex);
|
||||||
layer.IndexMap[item.MetaInfo.RuntimeTypeHandle] = i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lastIndex = layer.Count - 1;
|
||||||
|
for (int i = index; i < lastIndex; i++)
|
||||||
|
{
|
||||||
|
UIMetadata item = layer.Items[i + 1];
|
||||||
|
layer.Items[i] = item;
|
||||||
|
layer.TypeIdToIndex[item.MetaInfo.TypeId] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.Items[lastIndex] = null;
|
||||||
|
layer.Count = lastIndex;
|
||||||
|
layer.TypeIdToIndex[typeId] = -1;
|
||||||
|
|
||||||
UpdateFullscreenIndexAfterRemove(layer, meta, index);
|
UpdateFullscreenIndexAfterRemove(layer, meta, index);
|
||||||
return (index, previousFullscreenIndex);
|
return (index, previousFullscreenIndex);
|
||||||
}
|
}
|
||||||
@ -196,23 +264,25 @@ namespace AlicizaX.UI.Runtime
|
|||||||
private void MoveToTop(UIMetadata meta)
|
private void MoveToTop(UIMetadata meta)
|
||||||
{
|
{
|
||||||
var layer = _openUI[meta.MetaInfo.UILayer];
|
var layer = _openUI[meta.MetaInfo.UILayer];
|
||||||
int lastIdx = layer.OrderList.Count - 1;
|
int lastIdx = layer.Count - 1;
|
||||||
|
int typeId = meta.MetaInfo.TypeId;
|
||||||
|
|
||||||
if (!layer.IndexMap.TryGetValue(meta.MetaInfo.RuntimeTypeHandle, out int currentIdx))
|
if ((uint)typeId >= (uint)layer.TypeIdToIndex.Length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
int currentIdx = layer.TypeIdToIndex[typeId];
|
||||||
|
|
||||||
if (currentIdx != lastIdx && currentIdx >= 0)
|
if (currentIdx != lastIdx && currentIdx >= 0)
|
||||||
{
|
{
|
||||||
layer.OrderList.RemoveAt(currentIdx);
|
|
||||||
layer.OrderList.Add(meta);
|
|
||||||
|
|
||||||
for (int i = currentIdx; i < lastIdx; i++)
|
for (int i = currentIdx; i < lastIdx; i++)
|
||||||
{
|
{
|
||||||
var item = layer.OrderList[i];
|
UIMetadata item = layer.Items[i + 1];
|
||||||
layer.IndexMap[item.MetaInfo.RuntimeTypeHandle] = i;
|
layer.Items[i] = item;
|
||||||
|
layer.TypeIdToIndex[item.MetaInfo.TypeId] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.IndexMap[meta.MetaInfo.RuntimeTypeHandle] = lastIdx;
|
layer.Items[lastIdx] = meta;
|
||||||
|
layer.TypeIdToIndex[typeId] = lastIdx;
|
||||||
UpdateFullscreenIndexAfterMove(layer, meta, currentIdx, lastIdx);
|
UpdateFullscreenIndexAfterMove(layer, meta, currentIdx, lastIdx);
|
||||||
|
|
||||||
SortWindowDepth(meta.MetaInfo.UILayer, currentIdx);
|
SortWindowDepth(meta.MetaInfo.UILayer, currentIdx);
|
||||||
@ -220,41 +290,55 @@ namespace AlicizaX.UI.Runtime
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private async UniTask UpdateVisualState(UIMetadata meta, CancellationToken cancellationToken = default)
|
private async UniTask<bool> UpdateVisualState(UIMetadata meta, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
SortWindowVisible(meta.MetaInfo.UILayer);
|
SortWindowVisible(meta.MetaInfo.UILayer);
|
||||||
SortWindowDepth(meta.MetaInfo.UILayer);
|
SortWindowDepth(meta.MetaInfo.UILayer);
|
||||||
if (meta.State == UIState.Loaded)
|
if (meta.State == UIState.Loaded)
|
||||||
{
|
{
|
||||||
await meta.View.InternalInitlized(cancellationToken);
|
if (!await meta.View.InternalInitlized(cancellationToken))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await meta.View.InternalOpen(cancellationToken);
|
return await meta.View.InternalOpen(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool UpdateVisualStateSync(UIMetadata meta)
|
||||||
|
{
|
||||||
|
SortWindowVisible(meta.MetaInfo.UILayer);
|
||||||
|
SortWindowDepth(meta.MetaInfo.UILayer);
|
||||||
|
if (meta.State == UIState.Loaded)
|
||||||
|
{
|
||||||
|
if (!meta.View.InternalInitlizedSync()) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta.View.InternalOpenSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SortWindowVisible(int layer, int previousFullscreenIndex = int.MinValue)
|
private void SortWindowVisible(int layer, int previousFullscreenIndex = int.MinValue)
|
||||||
{
|
{
|
||||||
var layerData = _openUI[layer];
|
var layerData = _openUI[layer];
|
||||||
var list = layerData.OrderList;
|
int count = layerData.Count;
|
||||||
int count = list.Count;
|
|
||||||
|
|
||||||
int fullscreenIdx = layerData.LastFullscreenIndex;
|
int fullscreenIdx = layerData.LastFullscreenIndex;
|
||||||
if (fullscreenIdx >= count || (fullscreenIdx >= 0 && !IsDisplayFullscreen(list[fullscreenIdx])))
|
if (fullscreenIdx >= count || (fullscreenIdx >= 0 && !IsDisplayFullscreen(layerData.Items[fullscreenIdx])))
|
||||||
{
|
{
|
||||||
fullscreenIdx = FindLastFullscreenIndex(list, count - 1);
|
fullscreenIdx = FindLastFullscreenIndex(layerData, count - 1);
|
||||||
layerData.LastFullscreenIndex = fullscreenIdx;
|
layerData.LastFullscreenIndex = fullscreenIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
int oldFullscreenIndex = previousFullscreenIndex == int.MinValue ? fullscreenIdx : previousFullscreenIndex;
|
int oldFullscreenIndex = previousFullscreenIndex == int.MinValue ? fullscreenIdx : previousFullscreenIndex;
|
||||||
if (oldFullscreenIndex == fullscreenIdx)
|
if (oldFullscreenIndex == fullscreenIdx)
|
||||||
{
|
{
|
||||||
ApplyVisibilityRange(list, fullscreenIdx >= 0 ? fullscreenIdx : 0, count, fullscreenIdx);
|
ApplyVisibilityRange(layerData, fullscreenIdx >= 0 ? fullscreenIdx : 0, count, fullscreenIdx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldFullscreenIndex == -1 && fullscreenIdx == -1)
|
if (oldFullscreenIndex == -1 && fullscreenIdx == -1)
|
||||||
{
|
{
|
||||||
ApplyVisibilityRange(list, 0, count, -1);
|
ApplyVisibilityRange(layerData, 0, count, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,21 +349,21 @@ namespace AlicizaX.UI.Runtime
|
|||||||
? count
|
? count
|
||||||
: Math.Max(oldFullscreenIndex, fullscreenIdx) + 1;
|
: Math.Max(oldFullscreenIndex, fullscreenIdx) + 1;
|
||||||
|
|
||||||
ApplyVisibilityRange(list, start, endExclusive, fullscreenIdx);
|
ApplyVisibilityRange(layerData, start, endExclusive, fullscreenIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SortWindowDepth(int layer, int startIndex = 0)
|
private void SortWindowDepth(int layer, int startIndex = 0)
|
||||||
{
|
{
|
||||||
var list = _openUI[layer].OrderList;
|
var layerData = _openUI[layer];
|
||||||
int baseDepth = layer * LAYER_DEEP;
|
int baseDepth = layer * LAYER_DEEP;
|
||||||
|
|
||||||
for (int i = startIndex; i < list.Count; i++)
|
for (int i = startIndex; i < layerData.Count; i++)
|
||||||
{
|
{
|
||||||
int newDepth = baseDepth + i * WINDOW_DEEP;
|
int newDepth = baseDepth + i * WINDOW_DEEP;
|
||||||
|
|
||||||
if (list[i].View.Depth != newDepth)
|
if (layerData.Items[i].View.Depth != newDepth)
|
||||||
{
|
{
|
||||||
list[i].View.Depth = newDepth;
|
layerData.Items[i].View.Depth = newDepth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,11 +373,11 @@ namespace AlicizaX.UI.Runtime
|
|||||||
return meta.MetaInfo.FullScreen && UIStateMachine.IsDisplayActive(meta.State);
|
return meta.MetaInfo.FullScreen && UIStateMachine.IsDisplayActive(meta.State);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int FindLastFullscreenIndex(List<UIMetadata> list, int startIndex)
|
private static int FindLastFullscreenIndex(LayerData layer, int startIndex)
|
||||||
{
|
{
|
||||||
for (int i = Math.Min(startIndex, list.Count - 1); i >= 0; i--)
|
for (int i = Math.Min(startIndex, layer.Count - 1); i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (IsDisplayFullscreen(list[i]))
|
if (IsDisplayFullscreen(layer.Items[i]))
|
||||||
{
|
{
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@ -302,28 +386,28 @@ namespace AlicizaX.UI.Runtime
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ApplyVisibilityRange(List<UIMetadata> list, int startInclusive, int endExclusive, int fullscreenIdx)
|
private static void ApplyVisibilityRange(LayerData layer, int startInclusive, int endExclusive, int fullscreenIdx)
|
||||||
{
|
{
|
||||||
if (startInclusive < 0)
|
if (startInclusive < 0)
|
||||||
{
|
{
|
||||||
startInclusive = 0;
|
startInclusive = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endExclusive > list.Count)
|
if (endExclusive > layer.Count)
|
||||||
{
|
{
|
||||||
endExclusive = list.Count;
|
endExclusive = layer.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool showAll = fullscreenIdx < 0;
|
bool showAll = fullscreenIdx < 0;
|
||||||
for (int i = startInclusive; i < endExclusive; i++)
|
for (int i = startInclusive; i < endExclusive; i++)
|
||||||
{
|
{
|
||||||
list[i].View.Visible = showAll || i >= fullscreenIdx;
|
layer.Items[i].View.Visible = showAll || i >= fullscreenIdx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateFullscreenIndexAfterRemove(LayerData layer, UIMetadata removedMeta, int removedIndex)
|
private static void UpdateFullscreenIndexAfterRemove(LayerData layer, UIMetadata removedMeta, int removedIndex)
|
||||||
{
|
{
|
||||||
if (layer.OrderList.Count == 0)
|
if (layer.Count == 0)
|
||||||
{
|
{
|
||||||
layer.LastFullscreenIndex = -1;
|
layer.LastFullscreenIndex = -1;
|
||||||
return;
|
return;
|
||||||
@ -331,7 +415,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
if (removedMeta.MetaInfo.FullScreen && layer.LastFullscreenIndex == removedIndex)
|
if (removedMeta.MetaInfo.FullScreen && layer.LastFullscreenIndex == removedIndex)
|
||||||
{
|
{
|
||||||
layer.LastFullscreenIndex = FindLastFullscreenIndex(layer.OrderList, removedIndex - 1);
|
layer.LastFullscreenIndex = FindLastFullscreenIndex(layer, removedIndex - 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace AlicizaX.UI.Runtime
|
namespace AlicizaX.UI.Runtime
|
||||||
{
|
{
|
||||||
internal sealed partial class UIService : ServiceBase, IUIService, IServiceTickable
|
internal sealed partial class UIService : ServiceBase, IUIService, IUIDebugService, IServiceTickable
|
||||||
{
|
{
|
||||||
private ITimerService _timerService;
|
private ITimerService _timerService;
|
||||||
|
|
||||||
@ -24,16 +24,16 @@ namespace AlicizaX.UI.Runtime
|
|||||||
for (int layerIndex = 0; layerIndex < _openUI.Length; layerIndex++)
|
for (int layerIndex = 0; layerIndex < _openUI.Length; layerIndex++)
|
||||||
{
|
{
|
||||||
var layer = _openUI[layerIndex];
|
var layer = _openUI[layerIndex];
|
||||||
int count = layer.OrderList.Count;
|
int count = layer.Count;
|
||||||
if (count == 0) continue;
|
if (count == 0) continue;
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
if (layer.OrderList.Count != count)
|
if (layer.Count != count)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var window = layer.OrderList[i];
|
var window = layer.Items[i];
|
||||||
if (window.MetaInfo.NeedUpdate)
|
if (window.MetaInfo.NeedUpdate)
|
||||||
window.View.InternalUpdate();
|
window.View.InternalUpdate();
|
||||||
}
|
}
|
||||||
@ -110,10 +110,10 @@ namespace AlicizaX.UI.Runtime
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = layer.OrderList.Count;
|
int count = layer.Count;
|
||||||
for (int i = count - 1; i >= 0; i--)
|
for (int i = count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
UIMetadata meta = layer.OrderList[i];
|
UIMetadata meta = layer.Items[i];
|
||||||
if (meta == null)
|
if (meta == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@ -123,27 +123,21 @@ namespace AlicizaX.UI.Runtime
|
|||||||
meta.DisposeImmediate();
|
meta.DisposeImmediate();
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.OrderList.Clear();
|
Array.Clear(layer.Items, 0, layer.Count);
|
||||||
layer.IndexMap.Clear();
|
for (int i = 0; i < layer.TypeIdToIndex.Length; i++)
|
||||||
|
{
|
||||||
|
layer.TypeIdToIndex[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.Count = 0;
|
||||||
layer.LastFullscreenIndex = -1;
|
layer.LastFullscreenIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_CacheWindow.Count > 0)
|
if (m_CacheWindowCount > 0)
|
||||||
{
|
{
|
||||||
RuntimeTypeHandle[] handles = new RuntimeTypeHandle[m_CacheWindow.Count];
|
for (int i = m_CacheWindowCount - 1; i >= 0; i--)
|
||||||
int writeIndex = 0;
|
|
||||||
foreach (var pair in m_CacheWindow)
|
|
||||||
{
|
{
|
||||||
handles[writeIndex++] = pair.Key;
|
CacheEntry entry = m_CacheWindow[i];
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < writeIndex; i++)
|
|
||||||
{
|
|
||||||
if (!m_CacheWindow.TryGetValue(handles[i], out CacheEntry entry))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.TimerHandle != 0UL && _timerService != null)
|
if (entry.TimerHandle != 0UL && _timerService != null)
|
||||||
{
|
{
|
||||||
_timerService.RemoveTimer(entry.TimerHandle);
|
_timerService.RemoveTimer(entry.TimerHandle);
|
||||||
@ -152,9 +146,15 @@ namespace AlicizaX.UI.Runtime
|
|||||||
entry.Metadata.InCache = false;
|
entry.Metadata.InCache = false;
|
||||||
entry.Metadata.CancelAsyncOperations();
|
entry.Metadata.CancelAsyncOperations();
|
||||||
entry.Metadata.DisposeImmediate();
|
entry.Metadata.DisposeImmediate();
|
||||||
|
m_CacheWindow[i] = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_CacheWindow.Clear();
|
for (int i = 0; i < m_CacheTypeIdToIndex.Length; i++)
|
||||||
|
{
|
||||||
|
m_CacheTypeIdToIndex[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_CacheWindowCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_LastCountDownHandle != 0UL && _timerService != null)
|
if (m_LastCountDownHandle != 0UL && _timerService != null)
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
@ -10,12 +8,15 @@ namespace AlicizaX.UI.Runtime
|
|||||||
{
|
{
|
||||||
public abstract partial class UIBase
|
public abstract partial class UIBase
|
||||||
{
|
{
|
||||||
private readonly Dictionary<UIBase, UIMetadata> _children = new();
|
private UIMetadata[] _children = new UIMetadata[8];
|
||||||
private readonly List<UIMetadata> _updateableChildren = new();
|
private int[] _childTypeIdToIndex = CreateIndexArray(8);
|
||||||
|
private int _childCount;
|
||||||
|
private UIMetadata[] _updateableChildren = new UIMetadata[4];
|
||||||
|
private int _updateableChildCount;
|
||||||
|
|
||||||
private void UpdateChildren()
|
private void UpdateChildren()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _updateableChildren.Count; i++)
|
for (int i = 0; i < _updateableChildCount; i++)
|
||||||
{
|
{
|
||||||
var meta = _updateableChildren[i];
|
var meta = _updateableChildren[i];
|
||||||
if (meta.View.State == UIState.Opened)
|
if (meta.View.State == UIState.Opened)
|
||||||
@ -27,72 +28,44 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
private async UniTask DestroyAllChildren()
|
private async UniTask DestroyAllChildren()
|
||||||
{
|
{
|
||||||
var temp = ArrayPool<UIMetadata>.Shared.Rent(_children.Count);
|
while (_childCount > 0)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
int i = 0;
|
UIMetadata metadata = _children[--_childCount];
|
||||||
foreach (var kvp in _children)
|
_children[_childCount] = null;
|
||||||
|
ClearChildIndex(metadata);
|
||||||
|
if (metadata.View.Visible)
|
||||||
{
|
{
|
||||||
temp[i++] = kvp.Value;
|
metadata.CancelAsyncOperations();
|
||||||
|
metadata.EnsureCancellationToken();
|
||||||
|
await metadata.View.InternalClose(metadata.CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int j = 0; j < i; j++)
|
await metadata.DisposeAsync();
|
||||||
{
|
UIMetadataFactory.ReturnToPool(metadata);
|
||||||
UIMetadata metadata = temp[j];
|
|
||||||
if (metadata.View.Visible)
|
|
||||||
{
|
|
||||||
metadata.CancelAsyncOperations();
|
|
||||||
metadata.EnsureCancellationToken();
|
|
||||||
await metadata.View.InternalClose(metadata.CancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
await metadata.DisposeAsync();
|
|
||||||
UIMetadataFactory.ReturnToPool(metadata);
|
|
||||||
temp[j] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<UIMetadata>.Shared.Return(temp, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_children.Clear();
|
_updateableChildCount = 0;
|
||||||
_updateableChildren.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DestroyAllChildrenImmediate()
|
private void DestroyAllChildrenImmediate()
|
||||||
{
|
{
|
||||||
var temp = ArrayPool<UIMetadata>.Shared.Rent(_children.Count);
|
while (_childCount > 0)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
int i = 0;
|
UIMetadata metadata = _children[--_childCount];
|
||||||
foreach (var kvp in _children)
|
_children[_childCount] = null;
|
||||||
{
|
ClearChildIndex(metadata);
|
||||||
temp[i++] = kvp.Value;
|
metadata.DisposeImmediate();
|
||||||
}
|
UIMetadataFactory.ReturnToPool(metadata);
|
||||||
|
|
||||||
for (int j = 0; j < i; j++)
|
|
||||||
{
|
|
||||||
UIMetadata metadata = temp[j];
|
|
||||||
metadata.DisposeImmediate();
|
|
||||||
UIMetadataFactory.ReturnToPool(metadata);
|
|
||||||
temp[j] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<UIMetadata>.Shared.Return(temp, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_children.Clear();
|
_updateableChildCount = 0;
|
||||||
_updateableChildren.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChildVisible(bool value)
|
private void ChildVisible(bool value)
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<UIBase, UIMetadata> kvp in _children)
|
for (int i = 0; i < _childCount; i++)
|
||||||
{
|
{
|
||||||
UIBase view = kvp.Value.View;
|
UIBase view = _children[i].View;
|
||||||
if (view.State == UIState.Opened)
|
if (view.State == UIState.Opened)
|
||||||
{
|
{
|
||||||
view.Visible = value;
|
view.Visible = value;
|
||||||
@ -112,7 +85,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
{
|
{
|
||||||
metadata.CreateUI();
|
metadata.CreateUI();
|
||||||
UIHolderFactory.CreateUIResourceSync(metadata, parent, this);
|
UIHolderFactory.CreateUIResourceSync(metadata, parent, this);
|
||||||
ProcessWidget(metadata, visible).Forget();
|
ProcessWidgetSync(metadata, visible);
|
||||||
return (UIBase)metadata.View;
|
return (UIBase)metadata.View;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +140,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
metadata.CreateUI();
|
metadata.CreateUI();
|
||||||
UIBase widget = (UIBase)metadata.View;
|
UIBase widget = (UIBase)metadata.View;
|
||||||
widget.BindUIHolder(holder, this);
|
widget.BindUIHolder(holder, this);
|
||||||
ProcessWidget(metadata, true).Forget();
|
ProcessWidgetSync(metadata, true);
|
||||||
return (T)widget;
|
return (T)widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +158,11 @@ namespace AlicizaX.UI.Runtime
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await meta.View.InternalInitlized(cancellationToken);
|
if (!await meta.View.InternalInitlized(cancellationToken))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
meta.View.Visible = visible;
|
meta.View.Visible = visible;
|
||||||
if (meta.View.Visible)
|
if (meta.View.Visible)
|
||||||
{
|
{
|
||||||
@ -195,15 +172,23 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
private bool AddWidget(UIMetadata meta)
|
private bool AddWidget(UIMetadata meta)
|
||||||
{
|
{
|
||||||
if (!_children.TryAdd(meta.View, meta))
|
int typeId = meta.MetaInfo.TypeId;
|
||||||
|
EnsureChildIndexCapacity(typeId);
|
||||||
|
if (_childTypeIdToIndex[typeId] >= 0)
|
||||||
{
|
{
|
||||||
Log.Warning("Already has widget:{0}", meta.View);
|
Log.Warning("Already has widget:{0}", meta.View);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureChildCapacity();
|
||||||
|
int index = _childCount++;
|
||||||
|
_children[index] = meta;
|
||||||
|
_childTypeIdToIndex[typeId] = index;
|
||||||
|
|
||||||
if (meta.MetaInfo.NeedUpdate)
|
if (meta.MetaInfo.NeedUpdate)
|
||||||
{
|
{
|
||||||
_updateableChildren.Add(meta);
|
EnsureUpdateableChildCapacity();
|
||||||
|
_updateableChildren[_updateableChildCount++] = meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -211,7 +196,12 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
public async UniTask RemoveWidget(UIBase widget)
|
public async UniTask RemoveWidget(UIBase widget)
|
||||||
{
|
{
|
||||||
if (_children.Remove(widget, out var meta))
|
if (!TryRemoveChild(widget, out var meta))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta != null)
|
||||||
{
|
{
|
||||||
meta.CancelAsyncOperations();
|
meta.CancelAsyncOperations();
|
||||||
meta.EnsureCancellationToken();
|
meta.EnsureCancellationToken();
|
||||||
@ -229,18 +219,141 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
private void RemoveUpdateableChild(UIMetadata meta)
|
private void RemoveUpdateableChild(UIMetadata meta)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _updateableChildren.Count; i++)
|
for (int i = 0; i < _updateableChildCount; i++)
|
||||||
{
|
{
|
||||||
if (_updateableChildren[i] != meta)
|
if (_updateableChildren[i] != meta)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int lastIndex = _updateableChildren.Count - 1;
|
int lastIndex = _updateableChildCount - 1;
|
||||||
_updateableChildren[i] = _updateableChildren[lastIndex];
|
_updateableChildren[i] = _updateableChildren[lastIndex];
|
||||||
_updateableChildren.RemoveAt(lastIndex);
|
_updateableChildren[lastIndex] = null;
|
||||||
|
_updateableChildCount = lastIndex;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ProcessWidgetSync(UIMetadata meta, bool visible)
|
||||||
|
{
|
||||||
|
if (!AddWidget(meta))
|
||||||
|
{
|
||||||
|
meta.DisposeImmediate();
|
||||||
|
UIMetadataFactory.ReturnToPool(meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!meta.View.InternalInitlizedSync())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.View.Visible = visible;
|
||||||
|
if (meta.View.Visible)
|
||||||
|
{
|
||||||
|
meta.View.InternalOpenSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryRemoveChild(UIBase widget, out UIMetadata meta)
|
||||||
|
{
|
||||||
|
meta = null;
|
||||||
|
if (widget == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int typeId = widget.UITypeId;
|
||||||
|
if ((uint)typeId >= (uint)_childTypeIdToIndex.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = _childTypeIdToIndex[typeId];
|
||||||
|
if (index < 0 || index >= _childCount)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
meta = _children[index];
|
||||||
|
int lastIndex = _childCount - 1;
|
||||||
|
UIMetadata last = _children[lastIndex];
|
||||||
|
_children[index] = last;
|
||||||
|
_children[lastIndex] = null;
|
||||||
|
_childCount = lastIndex;
|
||||||
|
_childTypeIdToIndex[typeId] = -1;
|
||||||
|
if (index != lastIndex && last != null)
|
||||||
|
{
|
||||||
|
_childTypeIdToIndex[last.MetaInfo.TypeId] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearChildIndex(UIMetadata metadata)
|
||||||
|
{
|
||||||
|
if (metadata == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int typeId = metadata.MetaInfo.TypeId;
|
||||||
|
if ((uint)typeId < (uint)_childTypeIdToIndex.Length)
|
||||||
|
{
|
||||||
|
_childTypeIdToIndex[typeId] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureChildCapacity()
|
||||||
|
{
|
||||||
|
if (_childCount < _children.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Resize(ref _children, _children.Length << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureChildIndexCapacity(int typeId)
|
||||||
|
{
|
||||||
|
if ((uint)typeId < (uint)_childTypeIdToIndex.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oldLength = _childTypeIdToIndex.Length;
|
||||||
|
int newLength = oldLength;
|
||||||
|
while (newLength <= typeId)
|
||||||
|
{
|
||||||
|
newLength <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Resize(ref _childTypeIdToIndex, newLength);
|
||||||
|
for (int i = oldLength; i < newLength; i++)
|
||||||
|
{
|
||||||
|
_childTypeIdToIndex[i] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureUpdateableChildCapacity()
|
||||||
|
{
|
||||||
|
if (_updateableChildCount < _updateableChildren.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Resize(ref _updateableChildren, _updateableChildren.Length << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] CreateIndexArray(int capacity)
|
||||||
|
{
|
||||||
|
int[] values = new int[capacity];
|
||||||
|
for (int i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
values[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
protected System.Object[] UserDatas => _userDatas;
|
protected System.Object[] UserDatas => _userDatas;
|
||||||
|
|
||||||
private RuntimeTypeHandle _runtimeTypeHandle;
|
private RuntimeTypeHandle _runtimeTypeHandle;
|
||||||
|
private int _uiTypeId = -1;
|
||||||
|
|
||||||
internal RuntimeTypeHandle RuntimeTypeHandler
|
internal RuntimeTypeHandle RuntimeTypeHandler
|
||||||
{
|
{
|
||||||
@ -106,6 +107,19 @@ namespace AlicizaX.UI.Runtime
|
|||||||
Dispose(true);
|
Dispose(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal int UITypeId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_uiTypeId < 0 && UIMetaRegistry.TryGet(RuntimeTypeHandler, out UIMetaRegistry.UIMetaInfo metaInfo))
|
||||||
|
{
|
||||||
|
_uiTypeId = metaInfo.TypeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _uiTypeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
private void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed) return;
|
||||||
@ -204,81 +218,137 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
internal abstract void BindUIHolder(UIHolderObjectBase holder, UIBase owner);
|
internal abstract void BindUIHolder(UIHolderObjectBase holder, UIBase owner);
|
||||||
|
|
||||||
internal async UniTask InternalInitlized(CancellationToken cancellationToken = default)
|
internal async UniTask<bool> InternalInitlized(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Initialized))
|
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Initialized))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
_state = UIState.Initialized;
|
_state = UIState.Initialized;
|
||||||
Holder.OnWindowInitEvent?.Invoke();
|
Holder.OnWindowInitEvent?.Invoke();
|
||||||
await OnInitializeAsync();
|
await OnInitializeAsync();
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return false;
|
||||||
|
|
||||||
OnRegisterEvent(EventListenerProxy);
|
OnRegisterEvent(EventListenerProxy);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async UniTask InternalOpen(CancellationToken cancellationToken = default)
|
internal bool InternalInitlizedSync()
|
||||||
|
{
|
||||||
|
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Initialized))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_state = UIState.Initialized;
|
||||||
|
Holder.OnWindowInitEvent?.Invoke();
|
||||||
|
OnInitialize();
|
||||||
|
OnRegisterEvent(EventListenerProxy);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async UniTask<bool> InternalOpen(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (_state == UIState.Opened || _state == UIState.Opening)
|
if (_state == UIState.Opened || _state == UIState.Opening)
|
||||||
return;
|
return _state == UIState.Opened;
|
||||||
|
|
||||||
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Opening))
|
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Opening))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
int lifecycleVersion = BeginLifecycleTransition();
|
int lifecycleVersion = BeginLifecycleTransition();
|
||||||
_state = UIState.Opening;
|
_state = UIState.Opening;
|
||||||
Visible = true;
|
Visible = true;
|
||||||
Holder.OnWindowBeforeShowEvent?.Invoke();
|
Holder.OnWindowBeforeShowEvent?.Invoke();
|
||||||
try
|
await OnOpenAsync();
|
||||||
|
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Opening) || cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await OnOpenAsync();
|
RollbackOpeningState(lifecycleVersion);
|
||||||
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Opening))
|
return false;
|
||||||
return;
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
await Holder.PlayOpenTransitionAsync(cancellationToken);
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
||||||
|
bool openCanceled = await Holder.PlayOpenTransitionAsync(cancellationToken).SuppressCancellationThrow();
|
||||||
|
if (openCanceled || cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return;
|
RollbackOpeningState(lifecycleVersion);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Opening))
|
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Opening))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
_state = UIState.Opened;
|
_state = UIState.Opened;
|
||||||
Holder.OnWindowAfterShowEvent?.Invoke();
|
Holder.OnWindowAfterShowEvent?.Invoke();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async UniTask InternalClose(CancellationToken cancellationToken = default)
|
internal bool InternalOpenSync()
|
||||||
|
{
|
||||||
|
if (_state == UIState.Opened || _state == UIState.Opening)
|
||||||
|
return _state == UIState.Opened;
|
||||||
|
|
||||||
|
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Opening))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int lifecycleVersion = BeginLifecycleTransition();
|
||||||
|
_state = UIState.Opening;
|
||||||
|
Visible = true;
|
||||||
|
Holder.OnWindowBeforeShowEvent?.Invoke();
|
||||||
|
OnOpen();
|
||||||
|
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Opening))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_state = UIState.Opened;
|
||||||
|
Holder.OnWindowAfterShowEvent?.Invoke();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async UniTask<bool> InternalClose(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (_state == UIState.Closed || _state == UIState.Closing)
|
if (_state == UIState.Closed || _state == UIState.Closing)
|
||||||
return;
|
return _state == UIState.Closed;
|
||||||
|
|
||||||
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Closing))
|
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Closing))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
int lifecycleVersion = BeginLifecycleTransition();
|
int lifecycleVersion = BeginLifecycleTransition();
|
||||||
_state = UIState.Closing;
|
_state = UIState.Closing;
|
||||||
Holder.OnWindowBeforeClosedEvent?.Invoke();
|
Holder.OnWindowBeforeClosedEvent?.Invoke();
|
||||||
try
|
await OnCloseAsync();
|
||||||
{
|
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Closing) || cancellationToken.IsCancellationRequested)
|
||||||
await OnCloseAsync();
|
return false;
|
||||||
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Closing))
|
|
||||||
return;
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
bool closeCanceled = await Holder.PlayCloseTransitionAsync(cancellationToken).SuppressCancellationThrow();
|
||||||
await Holder.PlayCloseTransitionAsync(cancellationToken);
|
if (closeCanceled || cancellationToken.IsCancellationRequested)
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Closing))
|
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Closing))
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
Visible = false;
|
Visible = false;
|
||||||
_state = UIState.Closed;
|
_state = UIState.Closed;
|
||||||
Holder.OnWindowAfterClosedEvent?.Invoke();
|
Holder.OnWindowAfterClosedEvent?.Invoke();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool InternalCloseSync()
|
||||||
|
{
|
||||||
|
if (_state == UIState.Closed || _state == UIState.Closing)
|
||||||
|
return _state == UIState.Closed;
|
||||||
|
|
||||||
|
if (!UIStateMachine.ValidateTransition(GetType().Name, _state, UIState.Closing))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int lifecycleVersion = BeginLifecycleTransition();
|
||||||
|
_state = UIState.Closing;
|
||||||
|
Holder.OnWindowBeforeClosedEvent?.Invoke();
|
||||||
|
OnClose();
|
||||||
|
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Closing))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Visible = false;
|
||||||
|
_state = UIState.Closed;
|
||||||
|
Holder.OnWindowAfterClosedEvent?.Invoke();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void InternalUpdate()
|
internal void InternalUpdate()
|
||||||
@ -342,6 +412,15 @@ namespace AlicizaX.UI.Runtime
|
|||||||
return lifecycleVersion == _lifecycleVersion && _state == state;
|
return lifecycleVersion == _lifecycleVersion && _state == state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RollbackOpeningState(int lifecycleVersion)
|
||||||
|
{
|
||||||
|
if (!IsCurrentLifecycleTransition(lifecycleVersion, UIState.Opening))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Visible = false;
|
||||||
|
_state = UIState.Initialized;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,26 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI.Runtime
|
namespace AlicizaX.UI.Runtime
|
||||||
{
|
{
|
||||||
|
|
||||||
internal static class UIStateMachine
|
internal static class UIStateMachine
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<UIState, HashSet<UIState>> _validTransitions = new()
|
private static readonly ushort[] ValidTransitionMasks =
|
||||||
{
|
{
|
||||||
[UIState.Uninitialized] = new() { UIState.CreatedUI },
|
Mask(UIState.CreatedUI),
|
||||||
[UIState.CreatedUI] = new() { UIState.Loaded, UIState.Destroying },
|
Mask(UIState.Loaded, UIState.Destroying),
|
||||||
[UIState.Loaded] = new() { UIState.Initialized, UIState.Destroying },
|
Mask(UIState.Initialized, UIState.Destroying),
|
||||||
[UIState.Initialized] = new() { UIState.Opening, UIState.Destroying },
|
Mask(UIState.Opening, UIState.Destroying),
|
||||||
[UIState.Opening] = new() { UIState.Opened, UIState.Closing, UIState.Destroying },
|
Mask(UIState.Opened, UIState.Closing, UIState.Destroying),
|
||||||
[UIState.Opened] = new() { UIState.Closing, UIState.Destroying },
|
Mask(UIState.Closing, UIState.Destroying),
|
||||||
[UIState.Closing] = new() { UIState.Opening, UIState.Closed, UIState.Destroying },
|
Mask(UIState.Opening, UIState.Closed, UIState.Destroying),
|
||||||
[UIState.Closed] = new() { UIState.Opening, UIState.Destroying },
|
Mask(UIState.Opening, UIState.Destroying),
|
||||||
[UIState.Destroying] = new() { UIState.Destroyed },
|
Mask(UIState.Destroyed),
|
||||||
[UIState.Destroyed] = new() { }
|
0,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static bool IsValidTransition(UIState from, UIState to)
|
public static bool IsValidTransition(UIState from, UIState to)
|
||||||
{
|
{
|
||||||
return _validTransitions.TryGetValue(from, out var validStates) && validStates.Contains(to);
|
int fromIndex = (int)from;
|
||||||
|
return (uint)fromIndex < (uint)ValidTransitionMasks.Length && (ValidTransitionMasks[fromIndex] & Mask(to)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ValidateTransition(string uiName, UIState from, UIState to)
|
public static bool ValidateTransition(string uiName, UIState from, UIState to)
|
||||||
@ -30,16 +28,15 @@ namespace AlicizaX.UI.Runtime
|
|||||||
if (IsValidTransition(from, to))
|
if (IsValidTransition(from, to))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
Log.Error($"[UI] Invalid state transition for {uiName}: {from} -> {to}");
|
Log.Error(Cysharp.Text.ZString.Format("[UI] Invalid state transition for {0}: {1} -> {2}", uiName, from, to));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static HashSet<UIState> GetValidNextStates(UIState currentState)
|
public static ushort GetValidNextStateMask(UIState currentState)
|
||||||
{
|
{
|
||||||
return _validTransitions.TryGetValue(currentState, out var states)
|
int stateIndex = (int)currentState;
|
||||||
? states
|
return (uint)stateIndex < (uint)ValidTransitionMasks.Length ? ValidTransitionMasks[stateIndex] : (ushort)0;
|
||||||
: new HashSet<UIState>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetStateDescription(UIState state)
|
public static string GetStateDescription(UIState state)
|
||||||
@ -64,5 +61,20 @@ namespace AlicizaX.UI.Runtime
|
|||||||
{
|
{
|
||||||
return state == UIState.Opening || state == UIState.Opened || state == UIState.Closing;
|
return state == UIState.Opening || state == UIState.Opened || state == UIState.Closing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ushort Mask(UIState state)
|
||||||
|
{
|
||||||
|
return (ushort)(1 << (int)state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ushort Mask(UIState stateA, UIState stateB)
|
||||||
|
{
|
||||||
|
return (ushort)(Mask(stateA) | Mask(stateB));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ushort Mask(UIState stateA, UIState stateB, UIState stateC)
|
||||||
|
{
|
||||||
|
return (ushort)(Mask(stateA) | Mask(stateB) | Mask(stateC));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
|
using Cysharp.Text;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
@ -98,25 +99,19 @@ namespace AlicizaX.UI.Runtime
|
|||||||
private async UniTask StartAsyncLoading(RuntimeTypeHandle typeHandle, params System.Object[] userDatas)
|
private async UniTask StartAsyncLoading(RuntimeTypeHandle typeHandle, params System.Object[] userDatas)
|
||||||
{
|
{
|
||||||
_loadingFlags[typeHandle] = true;
|
_loadingFlags[typeHandle] = true;
|
||||||
|
UIMetadata metadata = UIMetadataFactory.GetWidgetMetadata(typeHandle);
|
||||||
|
Transform parent = _tabCache[typeHandle];
|
||||||
|
|
||||||
try
|
UIBase widget = await CreateWidgetUIAsync(metadata, parent, false);
|
||||||
|
_loadingFlags.Remove(typeHandle);
|
||||||
|
if (widget is UIWidget tabWidget)
|
||||||
{
|
{
|
||||||
UIMetadata metadata = UIMetadataFactory.GetWidgetMetadata(typeHandle);
|
|
||||||
Transform parent = _tabCache[typeHandle];
|
|
||||||
|
|
||||||
UIBase widget = await CreateWidgetUIAsync(metadata, parent, false);
|
|
||||||
if (widget is not UIWidget tabWidget) return;
|
|
||||||
|
|
||||||
_loadedTabs[typeHandle] = tabWidget;
|
_loadedTabs[typeHandle] = tabWidget;
|
||||||
SwitchToLoadedTab(tabWidget, userDatas);
|
SwitchToLoadedTab(tabWidget, userDatas);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
else
|
||||||
{
|
{
|
||||||
Debug.LogError($"Tab load failed: {e}");
|
Debug.LogError(ZString.Format("Tab load failed: {0}", Type.GetTypeFromHandle(typeHandle)?.Name));
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_loadingFlags.Remove(typeHandle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +128,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
{
|
{
|
||||||
if (index >= 0 && index < _typeOrder.Count) return true;
|
if (index >= 0 && index < _typeOrder.Count) return true;
|
||||||
|
|
||||||
Debug.LogError($"Invalid tab index: {index}");
|
Debug.LogError(ZString.Format("Invalid tab index: {0}", index));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,12 +13,12 @@ namespace AlicizaX.UI.Runtime
|
|||||||
public void Open(params System.Object[] userDatas)
|
public void Open(params System.Object[] userDatas)
|
||||||
{
|
{
|
||||||
RefreshParams(userDatas);
|
RefreshParams(userDatas);
|
||||||
InternalOpen().Forget();
|
InternalOpenSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
public void Close()
|
||||||
{
|
{
|
||||||
InternalClose().Forget();
|
InternalCloseSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Destroy()
|
public void Destroy()
|
||||||
|
|||||||
@ -15,6 +15,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
[SerializeField] private GameObject uiRoot = null;
|
[SerializeField] private GameObject uiRoot = null;
|
||||||
[SerializeField] private bool _isOrthographic = true;
|
[SerializeField] private bool _isOrthographic = true;
|
||||||
private Transform _instanceRoot = null;
|
private Transform _instanceRoot = null;
|
||||||
|
private const string CanvasScalerMissingMessage = "Not found CanvasScaler !";
|
||||||
|
|
||||||
private IUIService _uiService;
|
private IUIService _uiService;
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_uiService = AppServices.RegisterApp(new UIService());
|
_uiService = AppServices.RegisterApp<IUIService>(new UIService());
|
||||||
if (uiRoot == null)
|
if (uiRoot == null)
|
||||||
{
|
{
|
||||||
throw new GameFrameworkException("UIRoot Prefab is invalid.");
|
throw new GameFrameworkException("UIRoot Prefab is invalid.");
|
||||||
@ -54,7 +55,7 @@ namespace AlicizaX.UI.Runtime
|
|||||||
CanvasScaler scaler = _uiService.UICanvasRoot.GetComponent<CanvasScaler>();
|
CanvasScaler scaler = _uiService.UICanvasRoot.GetComponent<CanvasScaler>();
|
||||||
if (scaler == null)
|
if (scaler == null)
|
||||||
{
|
{
|
||||||
Log.Error($"Not found {nameof(CanvasScaler)} !");
|
Log.Error(CanvasScalerMissingMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user