using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Timers; using Cysharp.Threading.Tasks; using Microsoft.Extensions.Logging; namespace BITKit { /// /// 循环 /// public interface ITicker { /// /// 总帧数 /// ulong TickCount { get; } /// /// 在下一次循环时执行,仅执行一次 /// /// void Add(Action action); /// /// 注册在下一次循环时执行,每次循环都会执行 /// /// void Add(Action action); /// /// 移除循环 /// /// void Remove(Action action); /// /// 手动调用循环 /// /// void ManualTick(float delta); } /// /// 异步循环 /// public interface IAsyncTicker { ulong TickCount { get; } int TickRate { get; set; } bool IsConcurrent { get; set; } event Func OnTickAsync; } /// /// 主线程循环 /// public interface IMainTicker : ITicker { } /// /// 线程池循环 /// public interface IThreadTicker : ITicker { } #if UNITY_5_3_OR_NEWER /// /// 最后执行的循环,通常用于旋转、位移等 /// public interface IAfterTicker : ITicker{} /// /// Unity专用固定循环 /// public interface IFixedTicker : ITicker{} #endif public class AsyncTicker : IAsyncTicker,IDisposable { private readonly ILogger _logger; private readonly Timer _timer=new(); private bool _isDisposed; private readonly Stopwatch _stopwatch=new(); public AsyncTicker(ILogger logger) { _logger = logger; if (TickRate <= 0) { TickRate = 1; } _timer.Interval = 1000d / TickRate; _timer.Elapsed += OnElapsed; if (_isDisposed is false) _timer.Start(); _logger.LogInformation($"异步循环就绪,TickRate:{TickRate}"); } #if UNITY_5_3_OR_NEWER [UnityEngine.HideInCallstack] #endif private async void OnElapsed(object sender, ElapsedEventArgs e) { try { if(_isDisposed)return; if (IsSyncContext) { await BITApp.SwitchToMainThread(); #if UNITY_EDITOR await BITApp.SwitchToMainThread(); if (UnityEditor.EditorApplication.isPaused) { _timer.Interval = 1000d / TickRate; _timer.Start(); return; } #endif if(_isDisposed)return; } var deltaTime = 1f / TickRate; if (_stopwatch.IsRunning) { deltaTime = (float)_stopwatch.Elapsed.TotalSeconds; _stopwatch.Reset(); } _stopwatch.Start(); if (IsConcurrent) { var tasks = OnTickAsync.CastAsFunc().ToArray(); foreach (var func in tasks) { if (_isDisposed) return; try { await func.Invoke(deltaTime); } catch (Exception exception) { _logger.LogCritical(exception,exception.Message); } } } else { try { await OnTickAsync.UniTaskFunc(deltaTime); } catch (Exception exception) { _logger.LogCritical(exception,exception.Message); } } } catch (Exception exception) { _logger.LogCritical(exception,exception.Message); } TickCount++; if(_isDisposed)return; _timer.Interval = 1000d / TickRate; _timer.Start(); } public bool IsSyncContext { get; set; } = true; public ulong TickCount { get; set; } public int TickRate { get => _tickRate; set => _tickRate = Math.Clamp(value, 1, int.MaxValue); } private int _tickRate; public bool IsConcurrent { get; set; } public event Func OnTickAsync; public void Dispose() { _isDisposed = true; _timer.Stop(); _timer.Dispose(); } } public class Ticker : ITicker { private readonly Queue _queue = new(); private event Action TickEvents; public ulong TickCount { get; private set; } public void Add(Action action) { _queue.Enqueue(action); } public void Add(Action action)=>TickEvents += action; public void Remove(Action action)=>TickEvents -= action; public void ManualTick(float delta) { TickCount++; while (_queue.TryDequeue(out var action)) { action.Invoke(); } TickEvents?.Invoke(delta); } } }