[Optimization] TimerService&TimerDebug&AudioService

[Optimization] TimerService&TimerDebug&AudioService
This commit is contained in:
陈思海 2026-04-24 20:50:13 +08:00
parent a74715b468
commit f4f0ea1754
26 changed files with 1406 additions and 648 deletions

View File

@ -13,7 +13,6 @@ namespace AlicizaX.Audio.Editor
private readonly AudioCategoryDebugInfo _categoryInfo = new AudioCategoryDebugInfo();
private readonly AudioAgentDebugInfo _agentInfo = new AudioAgentDebugInfo();
private readonly AudioClipCacheDebugInfo _clipCacheInfo = new AudioClipCacheDebugInfo();
private SerializedProperty m_InstanceRoot = null;
private SerializedProperty m_AudioListener = null;
private SerializedProperty m_AudioMixer = null;
private SerializedProperty m_AudioGroupConfigs = null;
@ -31,7 +30,6 @@ namespace AlicizaX.Audio.Editor
EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
{
EditorGUILayout.PropertyField(m_InstanceRoot);
EditorGUILayout.PropertyField(m_AudioListener, AudioListenerLabel);
EditorGUILayout.PropertyField(m_AudioMixer);
EditorGUILayout.PropertyField(m_AudioGroupConfigs, GroupConfigLabel);
@ -51,7 +49,6 @@ namespace AlicizaX.Audio.Editor
private void OnEnable()
{
m_InstanceRoot = serializedObject.FindProperty("m_InstanceRoot");
m_AudioListener = serializedObject.FindProperty("m_AudioListener");
m_AudioMixer = serializedObject.FindProperty("m_AudioMixer");
m_AudioGroupConfigs = serializedObject.FindProperty("m_AudioGroupConfigs");

8
Editor/Timer.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d5eec662629e81c458ff9c776e1a9e29
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,169 @@
using AlicizaX.Editor;
using AlicizaX.Timer.Runtime;
using UnityEditor;
using UnityEngine;
namespace AlicizaX.Timer.Editor
{
[CustomEditor(typeof(TimerComponent))]
internal sealed class TimerComponentInspector : GameFrameworkInspector
{
private const double UPDATE_INTERVAL = 0.001d;
private const int MAX_DISPLAY_COUNT = 20;
private TimerDebugInfo[] _timerBuffer;
#if UNITY_EDITOR
private TimerDebugInfo[] _leakBuffer;
#endif
private double _lastUpdateTime;
private int _cachedActiveCount;
private int _cachedPoolCapacity;
private int _cachedPeakActiveCount;
private int _cachedFreeCount;
private string _cachedUsageText;
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
serializedObject.ApplyModifiedProperties();
DrawRuntimeDebugInfo();
if (EditorApplication.isPlaying)
{
double currentTime = EditorApplication.timeSinceStartup;
if (currentTime - _lastUpdateTime >= UPDATE_INTERVAL)
{
_lastUpdateTime = currentTime;
Repaint();
}
}
}
private void DrawRuntimeDebugInfo()
{
if (!EditorApplication.isPlaying)
{
EditorGUILayout.HelpBox("Available during runtime only.", MessageType.Info);
return;
}
if (!AppServices.TryGet<ITimerService>(out ITimerService timerService))
{
EditorGUILayout.HelpBox("Timer service is not initialized.", MessageType.Info);
return;
}
if (timerService is not ITimerServiceDebugView debugView)
{
return;
}
debugView.GetStatistics(out _cachedActiveCount, out _cachedPoolCapacity, out _cachedPeakActiveCount, out _cachedFreeCount);
_cachedUsageText = _cachedPoolCapacity > 0
? Utility.Text.Format("{0:F1}%", (float)_cachedActiveCount / _cachedPoolCapacity * 100f)
: "0.0%";
EditorGUILayout.Space();
EditorGUILayout.LabelField("Runtime Debug", EditorStyles.boldLabel);
EditorGUILayout.LabelField("Active Timers", _cachedActiveCount.ToString());
EditorGUILayout.LabelField("Pool Capacity", _cachedPoolCapacity.ToString());
EditorGUILayout.LabelField("Peak Active Count", _cachedPeakActiveCount.ToString());
EditorGUILayout.LabelField("Free Slots", _cachedFreeCount.ToString());
EditorGUILayout.LabelField("Pool Usage", _cachedUsageText);
DrawTimerList(debugView, _cachedActiveCount);
#if UNITY_EDITOR
DrawLeakDetection(debugView, _cachedActiveCount);
#endif
}
private void DrawTimerList(ITimerServiceDebugView debugView, int activeCount)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Active Timers", EditorStyles.boldLabel);
if (activeCount <= 0)
{
EditorGUILayout.LabelField("No active timers.");
return;
}
EnsureTimerBuffer(activeCount);
int timerCount = debugView.GetAllTimers(_timerBuffer);
int displayCount = Mathf.Min(timerCount, MAX_DISPLAY_COUNT);
if (displayCount < timerCount)
{
EditorGUILayout.HelpBox(Utility.Text.Format("Showing first {0} timers of {1}.", displayCount, timerCount), MessageType.Info);
}
for (int i = 0; i < displayCount; i++)
{
TimerDebugInfo timer = _timerBuffer[i];
string label = Utility.Text.Format(
"ID {0} | {1} | {2} | {3}",
timer.TimerId,
timer.IsLoop ? "Loop" : "Once",
timer.IsUnscaled ? "Unscaled" : "Scaled",
timer.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 DrawLeakDetection(ITimerServiceDebugView debugView, int activeCount)
{
if (activeCount <= 0)
{
return;
}
EnsureLeakBuffer(activeCount);
int staleCount = debugView.GetStaleOneShotTimers(_leakBuffer);
if (staleCount <= 0)
{
return;
}
EditorGUILayout.Space();
EditorGUILayout.LabelField(Utility.Text.Format("Stale One-Shot Timers ({0})", staleCount), EditorStyles.boldLabel);
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++)
{
TimerDebugInfo staleTimer = _leakBuffer[i];
EditorGUILayout.LabelField(
Utility.Text.Format("ID {0}", staleTimer.TimerId),
Utility.Text.Format("Created {0:F1}s ago", staleTimer.CreationTime));
}
}
#endif
private void EnsureTimerBuffer(int count)
{
if (_timerBuffer == null || _timerBuffer.Length < count)
{
_timerBuffer = new TimerDebugInfo[count];
}
}
#if UNITY_EDITOR
private void EnsureLeakBuffer(int count)
{
if (_leakBuffer == null || _leakBuffer.Length < count)
{
_leakBuffer = new TimerDebugInfo[count];
}
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02aa1426c358e87479136bd0f17f1f1c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,4 @@
#if ZSTRING_SUPPORT
using Cysharp.Text;
#endif
using Cysharp.Text;
namespace AlicizaX
{
@ -21,11 +19,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg);
#else
return string.Format(format, arg);
#endif
}
/// <summary>
@ -43,11 +38,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2);
#else
return string.Format(format, arg1, arg2);
#endif
}
/// <summary>
@ -67,11 +59,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3);
#else
return string.Format(format, arg1, arg2, arg3);
#endif
}
/// <summary>
@ -93,11 +82,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4);
#else
return string.Format(format, arg1, arg2, arg3, arg4);
#endif
}
/// <summary>
@ -121,11 +107,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5);
#endif
}
/// <summary>
@ -151,11 +134,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6);
#endif
}
/// <summary>
@ -183,11 +163,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
#endif
}
/// <summary>
@ -217,11 +194,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
#endif
}
/// <summary>
@ -253,11 +227,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
#endif
}
/// <summary>
@ -291,11 +262,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
#endif
}
/// <summary>
@ -331,11 +299,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11);
#endif
}
/// <summary>
@ -373,11 +338,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12);
#endif
}
/// <summary>
@ -417,11 +379,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13);
#endif
}
/// <summary>
@ -463,11 +422,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14);
#endif
}
/// <summary>
@ -511,11 +467,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15);
#endif
}
/// <summary>
@ -561,11 +514,8 @@ namespace AlicizaX
{
throw new GameFrameworkException("Format is invalid.");
}
#if ZSTRING_SUPPORT
return ZString.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16);
#else
return string.Format(format, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16);
#endif
}
}
}

View File

@ -21,11 +21,6 @@
"name": "com.alicizax.unity.animationflow",
"expression": "",
"define": "ALICIZAX_UI_ANIMATION_SUPPORT"
},
{
"name": "com.alicizax.unity.cysharp.zstring",
"expression": "2.3.0",
"define": "ZSTRING_SUPPORT"
}
],
"noEngineReferences": false

3
Runtime/AssemblyInfo.cs Normal file
View File

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("AlicizaX.Framework.Editor")]

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc79b52770c12304bbdd80020433741e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -25,6 +25,8 @@ namespace AlicizaX.Audio.Runtime
private ulong _handle;
private float _baseVolume;
private float _pitch;
private float _fadeInTimer;
private float _fadeInDuration;
private float _fadeTimer;
private float _fadeDuration;
private float _startedAt;
@ -37,7 +39,7 @@ namespace AlicizaX.Audio.Runtime
internal ulong Handle => _handle;
internal int Generation => _generation;
internal bool IsFree => _state == AudioAgentRuntimeState.Free;
internal bool IsPlayingState => _state == AudioAgentRuntimeState.Playing || _state == AudioAgentRuntimeState.Loading || _state == AudioAgentRuntimeState.Paused || _state == AudioAgentRuntimeState.FadingOut;
internal bool IsPlayingState => _state == AudioAgentRuntimeState.Playing || _state == AudioAgentRuntimeState.Loading || _state == AudioAgentRuntimeState.Paused || _state == AudioAgentRuntimeState.FadingIn || _state == AudioAgentRuntimeState.FadingOut;
internal float StartedAt => _startedAt;
internal void Initialize(AudioService service, AudioCategory category, int index, int globalIndex, AudioSourceObject sourceObject)
@ -67,6 +69,8 @@ namespace AlicizaX.Audio.Runtime
_startedAt = Time.realtimeSinceStartup;
_baseVolume = Mathf.Clamp01(request.Volume);
_pitch = request.Pitch <= 0f ? 1f : request.Pitch;
_fadeInDuration = request.FadeInSeconds > 0f ? request.FadeInSeconds : 0f;
_fadeInTimer = 0f;
_fadeDuration = request.FadeOutSeconds > 0f ? request.FadeOutSeconds : DefaultFadeOutSeconds;
_loop = request.Loop;
_spatial = request.Spatial || request.FollowTarget != null || request.UseWorldPosition;
@ -169,6 +173,22 @@ namespace AlicizaX.Audio.Runtime
UpdateFollowTarget();
UpdateOcclusion();
if (_state == AudioAgentRuntimeState.FadingIn)
{
_fadeInTimer += deltaTime;
if (_fadeInTimer >= _fadeInDuration)
{
_state = AudioAgentRuntimeState.Playing;
ApplyRuntimeVolume(1f);
}
else
{
float scale = _fadeInTimer / Mathf.Max(_fadeInDuration, MinFadeOutSeconds);
ApplyRuntimeVolume(scale);
}
return;
}
if (_state == AudioAgentRuntimeState.Playing)
{
if (!_loop && _source != null && !_source.isPlaying)
@ -252,9 +272,20 @@ namespace AlicizaX.Audio.Runtime
_source.clip = clip;
_source.loop = _loop;
ApplyRuntimeVolume(1f);
if (_fadeInDuration > 0f)
{
_fadeInTimer = 0f;
_state = AudioAgentRuntimeState.FadingIn;
ApplyRuntimeVolume(0f);
}
else
{
_state = AudioAgentRuntimeState.Playing;
ApplyRuntimeVolume(1f);
}
_source.Play();
_state = AudioAgentRuntimeState.Playing;
}
private void StopImmediate(bool notifyCategory)
@ -313,6 +344,8 @@ namespace AlicizaX.Audio.Runtime
_handle = 0;
_baseVolume = 1f;
_pitch = 1f;
_fadeInTimer = 0f;
_fadeInDuration = 0f;
_fadeTimer = 0f;
_fadeDuration = 0f;
_startedAt = 0f;

View File

@ -6,6 +6,7 @@ namespace AlicizaX.Audio.Runtime
Loading = 1,
Playing = 2,
Paused = 3,
FadingOut = 4
FadingIn = 4,
FadingOut = 5
}
}

View File

@ -1,5 +1,6 @@
using UnityEngine;
using UnityEngine.Audio;
using Cysharp.Text;
namespace AlicizaX.Audio.Runtime
{
@ -51,7 +52,7 @@ namespace AlicizaX.Audio.Runtime
_enabled = !config.Mute;
MixerGroup = ResolveMixerGroup(audioMixer, config);
InstanceRoot = new GameObject("Audio Category - " + Type).transform;
InstanceRoot = new GameObject(ZString.Concat("Audio Category - ", Type)).transform;
InstanceRoot.SetParent(service.InstanceRoot, false);
int capacity = config.AgentHelperCount;
@ -321,7 +322,7 @@ namespace AlicizaX.Audio.Runtime
private static AudioMixerGroup ResolveMixerGroup(AudioMixer audioMixer, AudioGroupConfig config)
{
AudioMixerGroup[] groups = audioMixer.FindMatchingGroups("Master/" + config.AudioType);
AudioMixerGroup[] groups = audioMixer.FindMatchingGroups(ZString.Concat("Master/", config.AudioType));
if (groups != null && groups.Length > 0)
{
return groups[0];

View File

@ -9,7 +9,6 @@ namespace AlicizaX.Audio.Runtime
public sealed class AudioComponent : MonoBehaviour
{
[SerializeField] private AudioMixer m_AudioMixer;
[SerializeField] private Transform m_InstanceRoot;
[SerializeField] private AudioListener m_AudioListener;
[SerializeField] private AudioGroupConfigCollection m_AudioGroupConfigs;
@ -22,10 +21,13 @@ namespace AlicizaX.Audio.Runtime
private void Start()
{
EnsureInstanceRoot();
EnsureAudioMixer();
if (m_AudioMixer == null)
{
throw new GameFrameworkException("AudioMixer is not assigned. Please assign an AudioMixer in the inspector.");
}
AudioGroupConfig[] configs = m_AudioGroupConfigs != null ? m_AudioGroupConfigs.GroupConfigs : null;
_audioService.Initialize(configs, m_InstanceRoot, m_AudioMixer);
_audioService.Initialize(configs, transform, m_AudioMixer);
if (m_AudioListener != null)
{
_audioService.RegisterListener(m_AudioListener);
@ -47,26 +49,5 @@ namespace AlicizaX.Audio.Runtime
_audioService.UnregisterListener(m_AudioListener);
}
}
private void EnsureInstanceRoot()
{
if (m_InstanceRoot != null)
{
return;
}
m_InstanceRoot = new GameObject("[AudioService Instances]").transform;
m_InstanceRoot.SetParent(transform, false);
m_InstanceRoot.localScale = Vector3.one;
}
private void EnsureAudioMixer()
{
if (m_AudioMixer == null)
{
m_AudioMixer = Resources.Load<AudioMixer>("AudioMixer");
}
}
}
}

View File

@ -22,6 +22,7 @@ namespace AlicizaX.Audio.Runtime
public float MaxDistance;
public AudioRolloffMode RolloffMode;
public bool OverrideSpatialSettings;
public float FadeInSeconds;
public float FadeOutSeconds;
public AudioPlayRequest()
@ -157,6 +158,7 @@ namespace AlicizaX.Audio.Runtime
MaxDistance = 500f;
RolloffMode = AudioRolloffMode.Logarithmic;
OverrideSpatialSettings = false;
FadeInSeconds = 0f;
FadeOutSeconds = 0.15f;
}

View File

@ -6,6 +6,7 @@ using AlicizaX.Resource.Runtime;
using UnityEngine;
using UnityEngine.Audio;
using YooAsset;
using Cysharp.Text;
namespace AlicizaX.Audio.Runtime
{
@ -31,6 +32,7 @@ namespace AlicizaX.Audio.Runtime
private readonly bool[] _categoryEnables = new bool[(int)AudioType.Max];
private readonly Dictionary<string, AudioClipCacheEntry> _clipCache = new Dictionary<string, AudioClipCacheEntry>(DefaultCacheCapacity, StringComparer.Ordinal);
private readonly AudioSourceObject[][] _sourceObjects = new AudioSourceObject[(int)AudioType.Max][];
private readonly Dictionary<AudioType, AudioGroupConfig> _configMap = new Dictionary<AudioType, AudioGroupConfig>((int)AudioType.Max);
private IResourceService _resourceService;
private IObjectPool<AudioSourceObject> _sourcePool;
@ -109,18 +111,41 @@ namespace AlicizaX.Audio.Runtime
{
Shutdown(false);
_resourceService = AppServices.Require<IResourceService>();
IObjectPoolService objectPoolService = AppServices.Require<IObjectPoolService>();
_sourcePool = objectPoolService.HasObjectPool<AudioSourceObject>(SourcePoolName)
? objectPoolService.GetObjectPool<AudioSourceObject>(SourcePoolName)
: objectPoolService.CreatePool<AudioSourceObject>(new ObjectPoolCreateOptions(SourcePoolName, false, 10f, int.MaxValue, float.MaxValue, 10));
if (audioGroupConfigs == null || audioGroupConfigs.Length == 0)
{
throw new GameFrameworkException("AudioGroupConfig[] is invalid.");
}
_configs = audioGroupConfigs;
BuildConfigMap();
InitializeObjectPools();
InitializeInstanceRoot(instanceRoot);
InitializeAudioMixer(audioMixer);
if (_unityAudioDisabled)
{
_initialized = true;
return;
}
InitializeHandleSystem();
InitializeCategories();
_initialized = true;
}
private void InitializeObjectPools()
{
_resourceService = AppServices.Require<IResourceService>();
IObjectPoolService objectPoolService = AppServices.Require<IObjectPoolService>();
_sourcePool = objectPoolService.HasObjectPool<AudioSourceObject>(SourcePoolName)
? objectPoolService.GetObjectPool<AudioSourceObject>(SourcePoolName)
: objectPoolService.CreatePool<AudioSourceObject>(new ObjectPoolCreateOptions(SourcePoolName, false, 10f, int.MaxValue, float.MaxValue, 10));
}
private void InitializeInstanceRoot(Transform instanceRoot)
{
if (instanceRoot != null)
{
_instanceRoot = instanceRoot;
@ -138,20 +163,25 @@ namespace AlicizaX.Audio.Runtime
{
UnityEngine.Object.DontDestroyOnLoad(_instanceRoot.gameObject);
}
}
private void InitializeAudioMixer(AudioMixer audioMixer)
{
_unityAudioDisabled = IsUnityAudioDisabled();
if (_unityAudioDisabled)
{
_initialized = true;
return;
}
_audioMixer = audioMixer != null ? audioMixer : Resources.Load<AudioMixer>("AudioMixer");
_audioMixer = audioMixer;
if (_audioMixer == null)
{
throw new GameFrameworkException("AudioMixer is invalid.");
throw new GameFrameworkException("AudioMixer is invalid. Please provide a valid AudioMixer.");
}
}
private void InitializeHandleSystem()
{
int totalAgentCount = 0;
for (int i = 0; i < (int)AudioType.Max; i++)
{
@ -169,7 +199,10 @@ namespace AlicizaX.Audio.Runtime
MemoryPool.Add<AudioPlayRequest>(totalAgentCount);
MemoryPool.Add<AudioLoadRequest>(totalAgentCount);
MemoryPool.Add<AudioClipCacheEntry>(_clipCacheCapacity);
}
private void InitializeCategories()
{
int globalIndexOffset = 0;
for (int i = 0; i < (int)AudioType.Max; i++)
{
@ -186,8 +219,6 @@ namespace AlicizaX.Audio.Runtime
globalIndexOffset += config.AgentHelperCount;
ApplyMixerVolume(config, _categoryVolumes[i], _categoryEnables[i]);
}
_initialized = true;
}
public void Restart()
@ -439,12 +470,17 @@ namespace AlicizaX.Audio.Runtime
}
public void ClearCache()
{
ClearCache(false);
}
private void ClearCache(bool force)
{
AudioClipCacheEntry entry = _allHead;
while (entry != null)
{
AudioClipCacheEntry next = entry.AllNext;
if (entry.RefCount <= 0 && !entry.Loading && entry.PendingHead == null)
if (force || (entry.RefCount <= 0 && !entry.Loading && entry.PendingHead == null))
{
RemoveClipEntry(entry);
}
@ -1073,7 +1109,7 @@ namespace AlicizaX.Audio.Runtime
private static string BuildSourceName(int typeIndex, int index)
{
return "AudioSource_" + typeIndex + "_" + index;
return ZString.Concat("AudioSource_", typeIndex, "_", index);
}
private AudioGroupConfig GetConfig(AudioType type)
@ -1087,23 +1123,27 @@ namespace AlicizaX.Audio.Runtime
return FindConfig(type);
}
private AudioGroupConfig FindConfig(AudioType type)
private void BuildConfigMap()
{
_configMap.Clear();
if (_configs == null)
{
return null;
return;
}
for (int i = 0; i < _configs.Length; i++)
{
AudioGroupConfig config = _configs[i];
if (config != null && config.AudioType == type)
if (config != null)
{
return config;
_configMap[config.AudioType] = config;
}
}
}
return null;
private AudioGroupConfig FindConfig(AudioType type)
{
return _configMap.TryGetValue(type, out AudioGroupConfig config) ? config : null;
}
private int CountActiveAgents()
@ -1143,7 +1183,7 @@ namespace AlicizaX.Audio.Runtime
}
}
ClearCache();
ClearCache(true);
if (_sourcePool != null)
{
_sourcePool.ReleaseAllUnused();
@ -1153,6 +1193,7 @@ namespace AlicizaX.Audio.Runtime
Array.Clear(_handleGenerations, 0, _handleGenerations.Length);
_handleAgents = Array.Empty<AudioAgent>();
_handleGenerations = Array.Empty<uint>();
_configMap.Clear();
_resourceService = null;
_sourcePool = null;
_audioMixer = null;

View File

@ -61,6 +61,25 @@ AudioMixerGroupController:
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!243 &-6728185375074428080
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Ambient - 2
m_AudioMixer: {fileID: 24100000}
m_GroupID: df8234ed398782540a4972841dfd6079
m_Children: []
m_Volume: 9d25aae74848d51418de553067dd0f0b
m_Pitch: 2018eb0e2888cff44bf523ce3d06fd0b
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: -2815600738436590526}
m_UserColorIndex: 0
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!243 &-6280614258348125054
AudioMixerGroupController:
m_ObjectHideFlags: 0
@ -80,6 +99,25 @@ AudioMixerGroupController:
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!243 &-5751393387862637061
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Ambient - 1
m_AudioMixer: {fileID: 24100000}
m_GroupID: 221f1da294156a3418168c39f16ae139
m_Children: []
m_Volume: 3a4c4abef80a7d74090c4558f2efbe8c
m_Pitch: bade8e01a57417d49bccd39245648de8
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: 588078238856344238}
m_UserColorIndex: 0
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!244 &-4958177229083455073
AudioMixerEffectController:
m_ObjectHideFlags: 3
@ -94,6 +132,25 @@ AudioMixerEffectController:
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!243 &-4470511876276122591
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Ambient - 3
m_AudioMixer: {fileID: 24100000}
m_GroupID: 62ab3b1c1e1517f4892acd6bbf63325e
m_Children: []
m_Volume: ae513375e8726984d88a822dcc805a47
m_Pitch: c670d6f9426d69c439bed5c1581cf095
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: 4372151859782775079}
m_UserColorIndex: 0
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!243 &-4372808504093502661
AudioMixerGroupController:
m_ObjectHideFlags: 0
@ -186,6 +243,43 @@ AudioMixerGroupController:
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!243 &-3339031547535134654
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Ambient
m_AudioMixer: {fileID: 24100000}
m_GroupID: eea06a873d29c174d9b93c8a87f28f29
m_Children:
- {fileID: -1635570523224726303}
- {fileID: -5751393387862637061}
- {fileID: -6728185375074428080}
- {fileID: -4470511876276122591}
m_Volume: 41dabee1ece434b46b30b1bf6008f4eb
m_Pitch: fef56cb47e7fcb845ae02cd29b60365e
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: -1709768828366853691}
m_UserColorIndex: 0
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!244 &-2815600738436590526
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Attenuation
m_EffectID: 89fdfdacd88dd56428258e46d756e1ea
m_EffectName: Attenuation
m_MixLevel: c67a0995f8d7b524ba49eeeca5216f58
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!243 &-2659745067392564156
AudioMixerGroupController:
m_ObjectHideFlags: 0
@ -219,6 +313,20 @@ AudioMixerEffectController:
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!244 &-1709768828366853691
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_EffectID: 65bb23c9d872c5347a34674e11b855f2
m_EffectName: Attenuation
m_MixLevel: eeea8e411d955da479365f63ad7834fd
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!243 &-1649243360580130678
AudioMixerGroupController:
m_ObjectHideFlags: 0
@ -238,6 +346,39 @@ AudioMixerGroupController:
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!243 &-1635570523224726303
AudioMixerGroupController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Ambient - 0
m_AudioMixer: {fileID: 24100000}
m_GroupID: dfba3d60853b22649ac844b8e925e0f6
m_Children: []
m_Volume: d292610df4aee5543b1124a0f1048e76
m_Pitch: 2d28f5f5a88e080488060323fd5d5d55
m_Send: 00000000000000000000000000000000
m_Effects:
- {fileID: -1114540582412186105}
m_UserColorIndex: 0
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!244 &-1114540582412186105
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_EffectID: 10f22744f37e0a143adcb17b39acf14a
m_EffectName: Attenuation
m_MixLevel: c4b8d05d2d796eb46b9288ae6451e044
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!244 &-998299258853400712
AudioMixerEffectController:
m_ObjectHideFlags: 3
@ -359,6 +500,11 @@ AudioMixerController:
- e012b6d2e0501df43a88eb6beff8ae07
- e84c25a476798ea43a2f6de217af7dba
- 98657376d4096a947953ee04d82830c1
- eea06a873d29c174d9b93c8a87f28f29
- dfba3d60853b22649ac844b8e925e0f6
- 221f1da294156a3418168c39f16ae139
- df8234ed398782540a4972841dfd6079
- 62ab3b1c1e1517f4892acd6bbf63325e
name: View
m_CurrentViewIndex: 0
m_TargetSnapshot: {fileID: 24500006}
@ -376,6 +522,7 @@ AudioMixerGroupController:
- {fileID: 7235523536312936115}
- {fileID: 7185772616558441635}
- {fileID: -3395020342500439107}
- {fileID: -3339031547535134654}
m_Volume: ba83e724007d7e9459f157db3a54a741
m_Pitch: a2d2b77391464bb4887f0bcd3835015b
m_Send: 00000000000000000000000000000000
@ -448,6 +595,20 @@ AudioMixerEffectController:
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!244 &588078238856344238
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Attenuation
m_EffectID: fe69f682bd0b54a42a947db1c2b9c800
m_EffectName: Attenuation
m_MixLevel: ca7defaf1a72196439b28b45a8ca0dc6
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!244 &1413273517213151576
AudioMixerEffectController:
m_ObjectHideFlags: 3
@ -533,6 +694,20 @@ AudioMixerGroupController:
m_Mute: 0
m_Solo: 0
m_BypassEffects: 0
--- !u!244 &4372151859782775079
AudioMixerEffectController:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Attenuation
m_EffectID: 3519e333bc2088840ad9986fa50cde94
m_EffectName: Attenuation
m_MixLevel: 04a4d4239cebc9348a0e8e88df14e74c
m_Parameters: []
m_SendTarget: {fileID: 0}
m_EnableWetMix: 0
m_Bypass: 0
--- !u!244 &5734415080786067514
AudioMixerEffectController:
m_ObjectHideFlags: 3

View File

@ -0,0 +1,200 @@
using AlicizaX.Timer.Runtime;
using UnityEngine;
using UnityEngine.UIElements;
namespace AlicizaX.Debugger.Runtime
{
public sealed partial class DebuggerComponent
{
private sealed class TimerInformationWindow : ScrollableDebuggerWindowBase
{
private const int MAX_DISPLAY_COUNT = 50;
private struct RowView
{
public VisualElement Root;
public Label Title;
public Label Value;
}
private ITimerServiceDebugView m_TimerDebugView;
private TimerDebugInfo[] m_TimerInfos;
private Label m_SectionTitleLabel;
private Label m_ActiveCountLabel;
private Label m_PoolCapacityLabel;
private Label m_PeakCountLabel;
private Label m_FreeCountLabel;
private Label m_UsageLabel;
private Label m_WarningLabel;
private RowView m_EmptyRow;
private readonly RowView[] m_TimerRows = new RowView[MAX_DISPLAY_COUNT];
public override void Initialize(params object[] args)
{
m_TimerDebugView = AppServices.Require<ITimerService>() as ITimerServiceDebugView;
}
protected override void BuildWindow(VisualElement root)
{
if (m_TimerDebugView == null)
{
return;
}
root.Add(CreateActionButton("Refresh", RefreshContent, DebuggerTheme.ButtonSurfaceActive, DebuggerTheme.PrimaryText));
VisualElement overview = CreateSection("Timer Pool Overview", out VisualElement overviewCard);
m_ActiveCountLabel = AddTextRow(overviewCard, "Active Timer Count").Value;
m_PoolCapacityLabel = AddTextRow(overviewCard, "Pool Capacity").Value;
m_PeakCountLabel = AddTextRow(overviewCard, "Peak Active Count").Value;
m_FreeCountLabel = AddTextRow(overviewCard, "Free Count").Value;
m_UsageLabel = AddTextRow(overviewCard, "Pool Usage").Value;
root.Add(overview);
VisualElement section = CreateSection("Active Timers", out VisualElement timerCard);
m_SectionTitleLabel = section.ElementAt(0) as Label;
m_WarningLabel = new Label();
m_WarningLabel.style.color = new Color(1f, 0.5f, 0f);
m_WarningLabel.style.display = DisplayStyle.None;
m_WarningLabel.style.marginBottom = 4f;
timerCard.Add(m_WarningLabel);
m_EmptyRow = AddTextRow(timerCard, string.Empty);
m_EmptyRow.Root.style.display = DisplayStyle.None;
for (int i = 0; i < MAX_DISPLAY_COUNT; i++)
{
m_TimerRows[i] = AddTextRow(timerCard, string.Empty);
m_TimerRows[i].Root.style.display = DisplayStyle.None;
}
root.Add(section);
RefreshContent();
}
private void RefreshContent()
{
if (m_TimerDebugView == null)
{
return;
}
m_TimerDebugView.GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
float poolUsage = poolCapacity > 0 ? (float)activeCount / poolCapacity : 0f;
m_ActiveCountLabel.text = activeCount.ToString();
m_PoolCapacityLabel.text = poolCapacity.ToString();
m_PeakCountLabel.text = peakActiveCount.ToString();
m_FreeCountLabel.text = freeCount.ToString();
m_UsageLabel.text = Utility.Text.Format("{0:P1}", poolUsage);
if (activeCount <= 0)
{
m_SectionTitleLabel.text = "Active Timers";
m_WarningLabel.style.display = DisplayStyle.None;
m_EmptyRow.Root.style.display = DisplayStyle.Flex;
m_EmptyRow.Title.text = "Status";
m_EmptyRow.Value.text = "No active timers";
SetTimerRowsVisible(0);
return;
}
EnsureTimerInfoBuffer(activeCount);
int timerCount = m_TimerDebugView.GetAllTimers(m_TimerInfos);
int displayCount = timerCount > MAX_DISPLAY_COUNT ? MAX_DISPLAY_COUNT : timerCount;
m_SectionTitleLabel.text = Utility.Text.Format("Active Timers ({0})", timerCount);
m_EmptyRow.Root.style.display = DisplayStyle.None;
if (displayCount < timerCount)
{
m_WarningLabel.text = Utility.Text.Format("Showing first {0} timers of {1}.", displayCount, timerCount);
m_WarningLabel.style.display = DisplayStyle.Flex;
}
else
{
m_WarningLabel.style.display = DisplayStyle.None;
}
for (int i = 0; i < displayCount; i++)
{
ref RowView row = ref m_TimerRows[i];
TimerDebugInfo info = m_TimerInfos[i];
row.Title.text = Utility.Text.Format("Timer #{0}", info.TimerId);
row.Value.text = Utility.Text.Format(
"{0} | {1} | {2} | Remaining: {3:F2}s | Duration: {4:F2}s",
info.IsLoop ? "Loop" : "Once",
info.IsRunning ? "Running" : "Paused",
info.IsUnscaled ? "Unscaled" : "Scaled",
info.LeftTime,
info.Duration);
row.Root.style.display = DisplayStyle.Flex;
}
SetTimerRowsVisible(displayCount);
}
private void SetTimerRowsVisible(int visibleCount)
{
for (int i = 0; i < MAX_DISPLAY_COUNT; i++)
{
m_TimerRows[i].Root.style.display = i < visibleCount ? DisplayStyle.Flex : DisplayStyle.None;
}
}
private RowView AddTextRow(VisualElement parent, string title)
{
float scale = DebuggerComponent.Instance != null ? DebuggerComponent.Instance.GetUiScale() : 1f;
VisualElement row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
row.style.minHeight = 36f * scale;
row.style.marginBottom = 4f * scale;
Label titleLabel = new Label(title);
titleLabel.style.minWidth = 280f * scale;
titleLabel.style.maxWidth = 280f * scale;
titleLabel.style.color = DebuggerTheme.SecondaryText;
titleLabel.style.fontSize = 18f * scale;
titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
titleLabel.style.flexShrink = 0f;
titleLabel.style.whiteSpace = WhiteSpace.Normal;
Label valueLabel = new Label();
valueLabel.style.flexGrow = 1f;
valueLabel.style.color = DebuggerTheme.PrimaryText;
valueLabel.style.fontSize = 18f * scale;
valueLabel.style.whiteSpace = WhiteSpace.Normal;
row.Add(titleLabel);
row.Add(valueLabel);
parent.Add(row);
RowView view;
view.Root = row;
view.Title = titleLabel;
view.Value = valueLabel;
return view;
}
private int EnsureTimerInfoBuffer(int count)
{
if (count <= 0)
{
if (m_TimerInfos == null || m_TimerInfos.Length == 0)
{
m_TimerInfos = new TimerDebugInfo[1];
}
return 0;
}
if (m_TimerInfos == null || m_TimerInfos.Length < count)
{
m_TimerInfos = new TimerDebugInfo[count];
}
return count;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8eac2550d41d14641b41a5c5523c4fde
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -93,6 +93,7 @@ namespace AlicizaX.Debugger.Runtime
private ObjectPoolInformationWindow m_ObjectPoolInformationWindow = new ObjectPoolInformationWindow();
private ReferencePoolInformationWindow m_ReferencePoolInformationWindow = new ReferencePoolInformationWindow();
private AudioInformationWindow m_AudioInformationWindow = new AudioInformationWindow();
private TimerInformationWindow m_TimerInformationWindow = new TimerInformationWindow();
private SettingsWindow m_SettingsWindow = new SettingsWindow();
private FpsCounter m_FpsCounter;
@ -502,6 +503,7 @@ namespace AlicizaX.Debugger.Runtime
RegisterDebuggerWindow("Profiler/Object Pool", m_ObjectPoolInformationWindow);
RegisterDebuggerWindow("Profiler/Reference Pool", m_ReferencePoolInformationWindow);
RegisterDebuggerWindow("Profiler/Audio", m_AudioInformationWindow);
RegisterDebuggerWindow("Profiler/Timer", m_TimerInformationWindow);
RegisterDebuggerWindow("Other/Settings", m_SettingsWindow);
}

View File

@ -4,6 +4,7 @@ using AlicizaX.Localization.Runtime;
using AlicizaX.ObjectPool;
using AlicizaX.Resource.Runtime;
using AlicizaX.Scene.Runtime;
using AlicizaX.Timer.Runtime;
using AlicizaX.UI.Runtime;
public static partial class GameApp

View File

@ -1,19 +1,17 @@
using System;
namespace AlicizaX
namespace AlicizaX.Timer.Runtime
{
[UnityEngine.Scripting.Preserve]
public interface ITimerService : IService
{
int AddTimer(TimerHandler callback, float time, bool isLoop = false, bool isUnscaled = false, params object[] args);
int AddTimer(TimerHandlerNoArgs callback, float time, bool isLoop = false, bool isUnscaled = false);
int AddTimer<T>(Action<T> callback, T arg, float time, bool isLoop = false, bool isUnscaled = false);
int AddTimer<T>(Action<T> callback, T arg, float time, bool isLoop = false, bool isUnscaled = false) where T : class;
void Stop(int timerId);
void Resume(int timerId);
bool IsRunning(int timerId);
float GetLeftTime(int timerId);
void Restart(int timerId);
void RemoveTimer(int timerId);
void RemoveAllTimer();
}
}

View File

@ -0,0 +1,27 @@
namespace AlicizaX.Timer.Runtime
{
internal struct TimerDebugInfo
{
public int TimerId;
public float LeftTime;
public float Duration;
public bool IsLoop;
public bool IsRunning;
public bool IsUnscaled;
#if UNITY_EDITOR
public float CreationTime;
#endif
}
internal interface ITimerServiceDebugView
{
int GetAllTimers(TimerDebugInfo[] results);
void GetStatistics(out int activeCount, out int poolCapacity, out int peakActiveCount, out int freeCount);
#if UNITY_EDITOR
int GetStaleOneShotTimers(TimerDebugInfo[] results);
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5b7cf36ada40b944f8c506e3cd8ddd12
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -10,6 +10,11 @@ namespace AlicizaX.Timer.Runtime
{
private void Awake()
{
if (AppServices.TryGet<ITimerService>(out _))
{
return;
}
AppServices.RegisterApp(new TimerService());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2
guid: 205d0803930745d7825f89aa604530a5
timeCreated: 1741683842
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using AlicizaX;
using AlicizaX.Timer.Runtime;
namespace AlicizaX.UI.Runtime
{
@ -46,8 +47,7 @@ namespace AlicizaX.UI.Runtime
uiMetadata,
uiMetadata.MetaInfo.CacheTime,
isLoop: false,
isUnscaled: true
);
isUnscaled: true);
if (timerId <= 0)
{