using System; using System.Collections.Generic; using UnityEngine; public static class KAsync { static KAsync() { KSystem.onUpdate -= UpdateFrame; KSystem.onUpdate += UpdateFrame; } #if UNITY_EDITOR [Serializable] internal partial class Info { public string description; } #endif internal partial class Info { private static int _counter; // user config public int delay; public Action callback; // internal internal bool alive = true; internal readonly int instID; internal readonly object id; public Info(object id) { instID = ++_counter; this.id = id; } public Info(Action callback, int delay, object id): this(id) { this.callback = callback; this.delay = delay; #if UNITY_EDITOR description = $"[{instID}] {GetType()}: {callback.Target}.{callback.Method.Name}();"; #endif } public virtual void Callback() { alive = false; try { callback?.Invoke(); } catch (Exception e) { Debug.LogWarning($"Exception: {e}"); } } } internal class Interval : Info { public readonly int interval; public Interval(Action callback, int delay, int interval, object id) : base(callback, delay, id) { this.interval = interval; } public override void Callback() { base.Callback(); delay = interval; alive = true; } } internal class Wait : Info { public readonly Func checkFunc; public readonly int interval; public Wait(Func checkFunc, Action onComplete, int interval, object id) : base(onComplete, 0, id) { this.checkFunc = checkFunc; this.interval = interval; } public override void Callback() { if (checkFunc == null) { alive = false; return; } var isFinish = checkFunc(); if (isFinish) { base.Callback(); return; } delay = interval; } } public static int frame; public static float time; public static float realTime; // [NonSerialized] private static int _sleepFrame; [NonSerialized] private static readonly List _execQueue = new List(); [NonSerialized] private static readonly List _queue = new List(); [NonSerialized] private static readonly Dictionary _map = new Dictionary(); private static T TryOverwrite(object id, Action callback, int delayInFrame) where T: Info { if (!_map.TryGetValue(id, out Info info)) return null; Debug.LogWarning($"Overwrite: {id}"); info.callback = callback; info.delay = delayInFrame; info.alive = true; return (T)info; } public static void DelayCall(Action callback, int delayInFrame = 0, object customId = null, bool tryOverwrite = true) { if (callback == null) return; // _sleepFrame = Mathf.Min(_sleepFrame, delayInFrame); var id = customId ?? callback; var canOverwrite = customId != null || tryOverwrite == true; if (canOverwrite && TryOverwrite(id, callback, delayInFrame) != null) return; var info = new Info(callback, delayInFrame, id); _queue.Add(info); _map.Add(id, info); } public static void SetInterval(Action callback, int delayInFrame, int intervalInFrame, object customId = null) { if (callback == null) return; // _sleepFrame = Mathf.Min(_sleepFrame, delayInFrame); var id = customId ?? callback; if (TryOverwrite(id, callback, delayInFrame) != null) return; var info = new Interval(callback, delayInFrame, intervalInFrame, id); _queue.Add(info); _map.Add(id, info); } public static void WaitUntil(Func check, Action onComplete, int checkInterval = 1, object customId = null) { if (check == null) return; var id = customId ?? check; // _sleepFrame = 0; if (_map.TryGetValue(id, out Info _)) { Debug.LogWarning("WaitUntil can not be overwritten!"); return; } var wait = new Wait(check, onComplete, checkInterval, id); _queue.Add(wait); _map.Add(id, wait); } public static void Kill(object id) { if (!_map.TryGetValue(id, out Info info)) return; _map.Remove(id); info.alive = false; } public static void Kill(Action cb) { Kill((object)cb); } public static void UpdateFrame() { frame++; time = Time.time; realTime = Time.realtimeSinceStartup; ProcessQueue(); } private static void ProcessQueue() { // Debug.LogWarning($"Process queue: {_queue.Count}"); var dieCount = 0; // _sleepFrame = 1000; _execQueue.Clear(); for (var i = 0; i < _queue.Count; i++) { Info q = _queue[i]; if (q == null) { dieCount++; continue; } if (q.alive == false) { _map.Remove(_queue[i].id); _queue[i] = null; dieCount++; continue; } if (q.delay > 0) { q.delay--; // if (q.delay % 10 == 0) // { // Debug.LogWarning($"{i} --> Wait: {q.delay}"); // } // _sleepFrame = Mathf.Min(q.delay, _sleepFrame); continue; } _execQueue.Add(q); } // compact queue if (dieCount > 8 && dieCount >= _queue.Count / 2f) // don't compact too small array it's useless! { var n = _queue.Count; for (var i = n - 1; i >= 0; i--) { if (_queue[i] == null) _queue.RemoveAt(i); } // Debug.Log($"After compact {n} --> {_queue.Count}"); } for (var i = 0; i < _execQueue.Count; i++) { Info info = _execQueue[i]; info.Callback(); } } }