Advertisement
VirtualMaestro

Interval method invoker

Jan 11th, 2025
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 10.46 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.LowLevel;
  5. using UnityEngine.PlayerLoop;
  6.  
  7. namespace Unidirect.Unity.Helpers
  8. {
  9.     public static class DoM
  10.     {
  11.         private static LinkedListNode<CJob> _nextProcessNode;
  12.         private static LinkedList<CJob> _jobs;
  13.         private static List<CJob> _skipFrameJobs;
  14.  
  15.         [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
  16.         private static void _Initialize()
  17.         {
  18.             var subscription = new PlayerLoopSubscription<Update>(_Update);
  19.             Application.quitting += subscription.Dispose;
  20.            
  21.             _jobs = new LinkedList<CJob>();
  22.             _skipFrameJobs = new List<CJob>(4);
  23.         }
  24.  
  25.         private static void _Update()
  26.         {
  27.             _nextProcessNode = _jobs.First;
  28.             var deltaTime = Time.deltaTime;
  29.            
  30.             while (_nextProcessNode != null && _jobs.Count > 0) // all jobs were disposed during the traversal
  31.             {
  32.                 var job = _nextProcessNode.Value;
  33.                 _nextProcessNode = _nextProcessNode.Next;
  34.  
  35.                 if (!job.IsPaused && job.Process(deltaTime))
  36.                     job.Complete();
  37.             }
  38.            
  39.             if (_skipFrameJobs.Count > 0)
  40.             {
  41.                 foreach (var job in _skipFrameJobs)
  42.                     _jobs.AddLast(job.Node);
  43.                    
  44.                 _skipFrameJobs.Clear();
  45.             }
  46.         }
  47.        
  48.         /// <summary>
  49.         /// Invokes given function every given amount of seconds.
  50.         /// The process will start in the next frame.
  51.         /// </summary>
  52.         /// <param name="func">Function to invoke.</param>
  53.         /// <param name="seconds">Delay between invocations in seconds.</param>
  54.         ///
  55.         /// <param name="skipFrame">If 'true' the given function will be called starting from the next frame.</param>
  56.         public static CJob EverySeconds(Action func, float seconds, bool skipFrame = false)
  57.         {
  58.             var job = CJob.Get(func).InSeconds(seconds);
  59.  
  60.             return _RunJob(job, skipFrame);
  61.         }
  62.  
  63.         /// <summary>
  64.         /// Invokes given function every second.
  65.         /// </summary>
  66.         public static CJob EverySecond(Action func, bool skipFrame = false)
  67.         {
  68.             return EverySeconds(func, 1, skipFrame);
  69.         }
  70.  
  71.         /// <summary>
  72.         /// Invokes given function every given amount of frames.
  73.         /// </summary>
  74.         /// <param name="func">Function to invoke.</param>
  75.         /// <param name="frames">Delay between invocations in frames.</param>
  76.         /// <param name="skipFrame">If 'true' the given function will be called starting from the next frame.</param>
  77.         public static CJob EveryFrames(Action func, int frames, bool skipFrame = false)
  78.         {
  79.             var job = CJob.Get(func).InFrames(frames);
  80.            
  81.             return _RunJob(job, skipFrame);
  82.         }
  83.  
  84.         /// <summary>
  85.         /// Invokes given function every frame.
  86.         /// </summary>
  87.         public static CJob EveryFrame(Action func, bool skipFrame = false)
  88.         {
  89.             return EveryFrames(func, 1, skipFrame);
  90.         }
  91.  
  92.         /// <summary>
  93.         /// Invokes given function one time after a delay in a given amount of seconds.
  94.         /// </summary>
  95.         public static CJob OnceSeconds(Action func, float seconds)
  96.         {
  97.             var job = CJob.Get(func).InSeconds(seconds).Times(1);
  98.             return _RunJob(job, false);
  99.         }
  100.        
  101.         /// <summary>
  102.         /// Invokes given function one time after a delay in a given amount of frames.
  103.         /// </summary>
  104.         public static CJob OnceFrames(Action func, int frames)
  105.         {
  106.             var job = CJob.Get(func).InFrames(frames).Times(1);
  107.             return _RunJob(job, false);
  108.         }
  109.  
  110.         /// <summary>
  111.         /// Invokes given function once on the next frame.
  112.         /// </summary>
  113.         public static CJob OnceNextFrame(Action func)
  114.         {
  115.             var job = CJob.Get(func).Times(1);
  116.             return _RunJob(job, true);
  117.         }
  118.        
  119.         private static CJob _RunJob(CJob job, bool skipFrame)
  120.         {
  121.             if (skipFrame)
  122.                 _skipFrameJobs.Add(job);
  123.             else
  124.                 _jobs.AddLast(job.Node);
  125.  
  126.             job.OnDispose += _OnJobDispose;
  127.  
  128.             return job;
  129.         }
  130.        
  131.         private static void _OnJobDispose(CJob job)
  132.         {
  133.             job.OnDispose -= _OnJobDispose;
  134.            
  135.             if (job.Node == _nextProcessNode)
  136.                 _nextProcessNode = job.Node.Next;
  137.         }
  138.        
  139.         public class CJob
  140.         {
  141.             public event Action<CJob> OnComplete;
  142.             public event Action<CJob> OnDispose;
  143.  
  144.             public readonly LinkedListNode<CJob> Node;
  145.  
  146.             private float _initSeconds;
  147.             private int _initFrames;
  148.             private float _seconds;
  149.             private int _frames;
  150.             private float _delaySeconds;    // delay before start in seconds
  151.             private int _delayFrames;           // delay before start in frames
  152.             private bool _hasDelay;
  153.             private int _times;
  154.             private Action _method;
  155.  
  156.             public bool IsPaused { set; get; }
  157.             public bool IsDisposed => Node.List == null;
  158.  
  159.             private CJob(Action method, bool isPaused = false)
  160.             {
  161.                 Node = new LinkedListNode<CJob>(this);
  162.                 _Reset();
  163.                 Set(method, isPaused);
  164.             }
  165.  
  166.             private void Set(Action method, bool isPaused = false)
  167.             {
  168.                 _method = method;
  169.                
  170.                 IsPaused = isPaused;
  171.             }
  172.  
  173.             public CJob InFrames(int frames)
  174.             {
  175.                 _initFrames = frames;
  176.                 _frames = _initFrames;
  177.                
  178.                 return this;
  179.             }
  180.  
  181.             public CJob InSeconds(float seconds)
  182.             {
  183.                 _initSeconds = seconds;
  184.                 _seconds = _initSeconds;
  185.                
  186.                 return this;
  187.             }
  188.  
  189.             public CJob Times(int times)
  190.             {
  191.                 _times = times;
  192.                 return this;
  193.             }
  194.  
  195.             public CJob DelaySeconds(float seconds)
  196.             {
  197.                 _delaySeconds = seconds;
  198.                 _hasDelay = _delaySeconds > 0;
  199.                 return this;
  200.             }
  201.  
  202.             public CJob DelayFrames(int frames)
  203.             {
  204.                 _delayFrames = frames;
  205.                 _hasDelay = _delayFrames > 0;
  206.                 return this;
  207.             }
  208.  
  209.             // Returns 'true' if a job should be disposed
  210.             public bool Process(float timeStep)
  211.             {
  212.                 if (_hasDelay)
  213.                 {
  214.                     _delaySeconds -= timeStep;
  215.                     --_delayFrames;
  216.                    
  217.                     _hasDelay = _delaySeconds > 0 || _delayFrames > 0;
  218.                    
  219.                     return false;
  220.                 }
  221.                
  222.                 _seconds -= timeStep;
  223.                 --_frames;
  224.                
  225.                 if (_seconds > 0 || _frames > 0)
  226.                     return false;
  227.                
  228.                 _method();
  229.                    
  230.                 _seconds = _initSeconds;
  231.                 _frames = _initFrames;
  232.  
  233.                 if (_times < int.MaxValue)
  234.                 {
  235.                     --_times;
  236.                     _times = _times > 0 ? _times : 0;
  237.                 }
  238.  
  239.                 return _times == 0;
  240.             }
  241.  
  242.             public void Complete()
  243.             {
  244.                 OnComplete?.Invoke(this);
  245.                 Dispose();
  246.             }
  247.  
  248.             public void Dispose()
  249.             {
  250.                 if (Node.List == null)
  251.                     return;
  252.                
  253.                 OnDispose?.Invoke(this);
  254.                
  255.                 Node.List.Remove(Node);
  256.  
  257.                 _method = null;
  258.                
  259.                 Put(this);
  260.             }
  261.  
  262.             private void _Reset()
  263.             {
  264.                 _initSeconds = -1;
  265.                 _initFrames = -1;
  266.                 _seconds = -1;
  267.                 _frames = -1;
  268.                 _delaySeconds = 0;
  269.                 _delayFrames = 0;
  270.                 _hasDelay = false;
  271.                 IsPaused = false;
  272.                 _times = int.MaxValue;
  273.                 _method = null;
  274.             }
  275.  
  276.             // *** Pool ***//
  277.             private static readonly Stack<CJob> Pool = new(10);
  278.  
  279.             public static CJob Get(Action method, bool isPaused = false)
  280.             {
  281.                 if (Pool.Count == 0)
  282.                     return new CJob(method, isPaused);
  283.                
  284.                 var job = Pool.Pop();
  285.                 job.Set(method, isPaused);
  286.                 return job;
  287.             }
  288.            
  289.             public static void Clear()
  290.             {
  291.                 Pool.Clear();
  292.             }
  293.            
  294.             private static void Put(CJob cJob)
  295.             {
  296.                 cJob._Reset();
  297.                 Pool.Push(cJob);
  298.             }
  299.         }
  300.        
  301.         private class PlayerLoopSubscription<T> : IDisposable
  302.         {
  303.             private readonly Action _callback;
  304.  
  305.             public PlayerLoopSubscription(Action callback)
  306.             {
  307.                 _callback = callback;
  308.                 _Subscribe();
  309.             }
  310.  
  311.             private void _Invoke()
  312.             {
  313.                 _callback.Invoke();
  314.             }
  315.  
  316.             private void _Subscribe()
  317.             {
  318.                 var loop = PlayerLoop.GetCurrentPlayerLoop();
  319.                 ref var system = ref _Find<T>(loop);
  320.                 system.updateDelegate += _Invoke;
  321.                 PlayerLoop.SetPlayerLoop(loop);
  322.             }
  323.  
  324.             private void _Unsubscribe()
  325.             {
  326.                 var loop = PlayerLoop.GetCurrentPlayerLoop();
  327.                 ref var system = ref _Find<T>(loop);
  328.                 system.updateDelegate -= _Invoke;
  329.                 PlayerLoop.SetPlayerLoop(loop);
  330.             }
  331.  
  332.             public void Dispose()
  333.             {
  334.                 _Unsubscribe();
  335.             }
  336.  
  337.             private static ref PlayerLoopSystem _Find<T>(PlayerLoopSystem root)
  338.             {
  339.                 var len = root.subSystemList.Length;
  340.            
  341.                 for (var i = 0; i < len; i++)
  342.                     if (root.subSystemList[i].type == typeof(T))
  343.                         return ref root.subSystemList[i];
  344.  
  345.                 throw new Exception($"System of type '{typeof(T).Name}' not found inside system '{root.type.Name}'.");
  346.             }
  347.         }
  348.     }
  349. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement