Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.LowLevel;
- using UnityEngine.PlayerLoop;
- namespace Unidirect.Unity.Helpers
- {
- public static class DoM
- {
- private static LinkedListNode<CJob> _nextProcessNode;
- private static LinkedList<CJob> _jobs;
- private static List<CJob> _skipFrameJobs;
- [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
- private static void _Initialize()
- {
- var subscription = new PlayerLoopSubscription<Update>(_Update);
- Application.quitting += subscription.Dispose;
- _jobs = new LinkedList<CJob>();
- _skipFrameJobs = new List<CJob>(4);
- }
- private static void _Update()
- {
- _nextProcessNode = _jobs.First;
- var deltaTime = Time.deltaTime;
- while (_nextProcessNode != null && _jobs.Count > 0) // all jobs were disposed during the traversal
- {
- var job = _nextProcessNode.Value;
- _nextProcessNode = _nextProcessNode.Next;
- if (!job.IsPaused && job.Process(deltaTime))
- job.Complete();
- }
- if (_skipFrameJobs.Count > 0)
- {
- foreach (var job in _skipFrameJobs)
- _jobs.AddLast(job.Node);
- _skipFrameJobs.Clear();
- }
- }
- /// <summary>
- /// Invokes given function every given amount of seconds.
- /// The process will start in the next frame.
- /// </summary>
- /// <param name="func">Function to invoke.</param>
- /// <param name="seconds">Delay between invocations in seconds.</param>
- ///
- /// <param name="skipFrame">If 'true' the given function will be called starting from the next frame.</param>
- public static CJob EverySeconds(Action func, float seconds, bool skipFrame = false)
- {
- var job = CJob.Get(func).InSeconds(seconds);
- return _RunJob(job, skipFrame);
- }
- /// <summary>
- /// Invokes given function every second.
- /// </summary>
- public static CJob EverySecond(Action func, bool skipFrame = false)
- {
- return EverySeconds(func, 1, skipFrame);
- }
- /// <summary>
- /// Invokes given function every given amount of frames.
- /// </summary>
- /// <param name="func">Function to invoke.</param>
- /// <param name="frames">Delay between invocations in frames.</param>
- /// <param name="skipFrame">If 'true' the given function will be called starting from the next frame.</param>
- public static CJob EveryFrames(Action func, int frames, bool skipFrame = false)
- {
- var job = CJob.Get(func).InFrames(frames);
- return _RunJob(job, skipFrame);
- }
- /// <summary>
- /// Invokes given function every frame.
- /// </summary>
- public static CJob EveryFrame(Action func, bool skipFrame = false)
- {
- return EveryFrames(func, 1, skipFrame);
- }
- /// <summary>
- /// Invokes given function one time after a delay in a given amount of seconds.
- /// </summary>
- public static CJob OnceSeconds(Action func, float seconds)
- {
- var job = CJob.Get(func).InSeconds(seconds).Times(1);
- return _RunJob(job, false);
- }
- /// <summary>
- /// Invokes given function one time after a delay in a given amount of frames.
- /// </summary>
- public static CJob OnceFrames(Action func, int frames)
- {
- var job = CJob.Get(func).InFrames(frames).Times(1);
- return _RunJob(job, false);
- }
- /// <summary>
- /// Invokes given function once on the next frame.
- /// </summary>
- public static CJob OnceNextFrame(Action func)
- {
- var job = CJob.Get(func).Times(1);
- return _RunJob(job, true);
- }
- private static CJob _RunJob(CJob job, bool skipFrame)
- {
- if (skipFrame)
- _skipFrameJobs.Add(job);
- else
- _jobs.AddLast(job.Node);
- job.OnDispose += _OnJobDispose;
- return job;
- }
- private static void _OnJobDispose(CJob job)
- {
- job.OnDispose -= _OnJobDispose;
- if (job.Node == _nextProcessNode)
- _nextProcessNode = job.Node.Next;
- }
- public class CJob
- {
- public event Action<CJob> OnComplete;
- public event Action<CJob> OnDispose;
- public readonly LinkedListNode<CJob> Node;
- private float _initSeconds;
- private int _initFrames;
- private float _seconds;
- private int _frames;
- private float _delaySeconds; // delay before start in seconds
- private int _delayFrames; // delay before start in frames
- private bool _hasDelay;
- private int _times;
- private Action _method;
- public bool IsPaused { set; get; }
- public bool IsDisposed => Node.List == null;
- private CJob(Action method, bool isPaused = false)
- {
- Node = new LinkedListNode<CJob>(this);
- _Reset();
- Set(method, isPaused);
- }
- private void Set(Action method, bool isPaused = false)
- {
- _method = method;
- IsPaused = isPaused;
- }
- public CJob InFrames(int frames)
- {
- _initFrames = frames;
- _frames = _initFrames;
- return this;
- }
- public CJob InSeconds(float seconds)
- {
- _initSeconds = seconds;
- _seconds = _initSeconds;
- return this;
- }
- public CJob Times(int times)
- {
- _times = times;
- return this;
- }
- public CJob DelaySeconds(float seconds)
- {
- _delaySeconds = seconds;
- _hasDelay = _delaySeconds > 0;
- return this;
- }
- public CJob DelayFrames(int frames)
- {
- _delayFrames = frames;
- _hasDelay = _delayFrames > 0;
- return this;
- }
- // Returns 'true' if a job should be disposed
- public bool Process(float timeStep)
- {
- if (_hasDelay)
- {
- _delaySeconds -= timeStep;
- --_delayFrames;
- _hasDelay = _delaySeconds > 0 || _delayFrames > 0;
- return false;
- }
- _seconds -= timeStep;
- --_frames;
- if (_seconds > 0 || _frames > 0)
- return false;
- _method();
- _seconds = _initSeconds;
- _frames = _initFrames;
- if (_times < int.MaxValue)
- {
- --_times;
- _times = _times > 0 ? _times : 0;
- }
- return _times == 0;
- }
- public void Complete()
- {
- OnComplete?.Invoke(this);
- Dispose();
- }
- public void Dispose()
- {
- if (Node.List == null)
- return;
- OnDispose?.Invoke(this);
- Node.List.Remove(Node);
- _method = null;
- Put(this);
- }
- private void _Reset()
- {
- _initSeconds = -1;
- _initFrames = -1;
- _seconds = -1;
- _frames = -1;
- _delaySeconds = 0;
- _delayFrames = 0;
- _hasDelay = false;
- IsPaused = false;
- _times = int.MaxValue;
- _method = null;
- }
- // *** Pool ***//
- private static readonly Stack<CJob> Pool = new(10);
- public static CJob Get(Action method, bool isPaused = false)
- {
- if (Pool.Count == 0)
- return new CJob(method, isPaused);
- var job = Pool.Pop();
- job.Set(method, isPaused);
- return job;
- }
- public static void Clear()
- {
- Pool.Clear();
- }
- private static void Put(CJob cJob)
- {
- cJob._Reset();
- Pool.Push(cJob);
- }
- }
- private class PlayerLoopSubscription<T> : IDisposable
- {
- private readonly Action _callback;
- public PlayerLoopSubscription(Action callback)
- {
- _callback = callback;
- _Subscribe();
- }
- private void _Invoke()
- {
- _callback.Invoke();
- }
- private void _Subscribe()
- {
- var loop = PlayerLoop.GetCurrentPlayerLoop();
- ref var system = ref _Find<T>(loop);
- system.updateDelegate += _Invoke;
- PlayerLoop.SetPlayerLoop(loop);
- }
- private void _Unsubscribe()
- {
- var loop = PlayerLoop.GetCurrentPlayerLoop();
- ref var system = ref _Find<T>(loop);
- system.updateDelegate -= _Invoke;
- PlayerLoop.SetPlayerLoop(loop);
- }
- public void Dispose()
- {
- _Unsubscribe();
- }
- private static ref PlayerLoopSystem _Find<T>(PlayerLoopSystem root)
- {
- var len = root.subSystemList.Length;
- for (var i = 0; i < len; i++)
- if (root.subSystemList[i].type == typeof(T))
- return ref root.subSystemList[i];
- throw new Exception($"System of type '{typeof(T).Name}' not found inside system '{root.type.Name}'.");
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement