Created
November 1, 2017 12:47
-
-
Save ByronMayne/5486e58c290ad9f35c7311067c81cfdf to your computer and use it in GitHub Desktop.
Delay Call, A simple to use script used to replace using coroutines to delay functions in Unity.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Reflection; | |
using UnityEngine; | |
public static class DelayCall | |
{ | |
public delegate void DelayedDelegate(); | |
public delegate void DelayedDelegate<T>(T parameter); | |
private static int DEFAULT_POOL_SIZE = 5; | |
private static int POOL_GROWTH_STEP_SIZE = 3; | |
private static ushort _nextCallID = 0; | |
private static bool _isSubscribedToUpdate; | |
private static bool _shouldStaySubscribedToUpdate; | |
private enum DelayType | |
{ | |
NotAllocated, | |
Seconds, | |
FrameNumber, | |
} | |
private struct DelayedCall | |
{ | |
private static readonly object[] NO_ARGS = new object[0]; | |
private static readonly object[] ONE_ARG = new object[1]; | |
public readonly ushort ID; | |
public DelayType delayType; | |
private int _frameNumber; | |
private float _secondsRemaing; | |
private MethodInfo _methodInfo; | |
private object _target; | |
private object _argument; | |
private bool _hasArgument; | |
/// <summary> | |
/// Creates a new instance of delayed call with a unique id. | |
/// </summary> | |
/// <param name="id"></param> | |
public DelayedCall(ushort id) | |
{ | |
ID = id; | |
_secondsRemaing = 0f; | |
delayType = DelayType.NotAllocated; | |
_hasArgument = false; | |
_argument = null; | |
_target = null; | |
_methodInfo = null; | |
_frameNumber = 0; | |
} | |
/// <summary> | |
/// Updates the internal state of the delayed call. Returns true when it's | |
/// active and false when it's not. | |
/// </summary> | |
public bool Tick() | |
{ | |
switch (delayType) | |
{ | |
case DelayType.NotAllocated: | |
// Nothing to do here | |
return false; | |
case DelayType.Seconds: | |
_secondsRemaing -= Time.deltaTime; | |
if (_secondsRemaing <= 0.0f) | |
{ | |
Invoke(); | |
Reset(); | |
return false; | |
} | |
return true; | |
case DelayType.FrameNumber: | |
if (_frameNumber <= Time.frameCount) | |
{ | |
Invoke(); | |
Reset(); | |
return false; | |
} | |
return true; | |
} | |
return false; | |
} | |
/// <summary> | |
/// Sets up this delayed call to use seconds. | |
/// </summary> | |
public void SetToSeconds(Delegate callback, float seconds, object argument, bool hasArgument) | |
{ | |
_secondsRemaing = seconds; | |
delayType = DelayType.Seconds; | |
_target = callback.Target; | |
_methodInfo = callback.Method; | |
_hasArgument = hasArgument; | |
_argument = argument; | |
} | |
/// <summary> | |
/// Sets up this delayed call to use frames. | |
/// </summary> | |
public void SetToFrames(Delegate callback, int frameNumber, object argument, bool hasArgument) | |
{ | |
_frameNumber = frameNumber; | |
delayType = DelayType.FrameNumber; | |
_target = callback.Target; | |
_methodInfo = callback.Method; | |
_hasArgument = hasArgument; | |
_argument = argument; | |
} | |
/// <summary> | |
/// Resets this delegate back to it's default state. | |
/// </summary> | |
public void Reset() | |
{ | |
delayType = DelayType.NotAllocated; | |
_secondsRemaing = 0.0f; | |
_frameNumber = 0; | |
} | |
/// <summary> | |
/// Calls the function of our delegate and then resets it. | |
/// </summary> | |
private void Invoke() | |
{ | |
if (_hasArgument) | |
{ | |
ONE_ARG[0] = _argument; | |
_methodInfo.Invoke(_target, ONE_ARG); | |
} | |
else | |
{ | |
_methodInfo.Invoke(_target, NO_ARGS); | |
} | |
Reset(); | |
} | |
} | |
private static DelayedCall[] _delayedCalls; | |
/// <summary> | |
/// Invoked the first frame that someone reference this class. | |
/// </summary> | |
static DelayCall() | |
{ | |
// Create our new array with our default size. | |
_delayedCalls = new DelayedCall[DEFAULT_POOL_SIZE]; | |
// Loop over an initialize them all | |
for (int i = 0; i < DEFAULT_POOL_SIZE; i++) | |
{ | |
_delayedCalls[i] = new DelayedCall(_nextCallID); | |
_nextCallID++; | |
} | |
} | |
/// <summary> | |
/// Takes a function and invokes it on the following frame. | |
/// </summary> | |
/// <param name="delayedCall">The function you want to invoke on the next frame.</param> | |
/// <returns>The ID of the delayed function. Used to cancel it with <see cref="CancelCall"/></returns> | |
public static ushort ToNextFrame(DelayedDelegate delayedCall) | |
{ | |
return AllocateCall(delayedCall, DelayType.FrameNumber, 0f, Time.frameCount + 1, null, false, 0); | |
} | |
/// <summary> | |
/// Takes a function and invokes it on the following frame and takes | |
/// one parameter. | |
/// </summary> | |
/// <typeparam name="T">The type of the parameter of the function.</typeparam> | |
/// <param name="delayedCall">The function you want to call.</param> | |
/// <param name="argument">The value of the parameter you want to send</param> | |
/// <returns>The ID of the delayed function. Used to cancel it with <see cref="CancelCall"/></returns> | |
public static ushort ToNextFrame<T>(DelayedDelegate<T> delayedCall, T argument) | |
{ | |
return AllocateCall(delayedCall, DelayType.FrameNumber, 0f, Time.frameCount + 1, argument, true, 0); | |
} | |
/// <summary> | |
/// Delays a function call by a set number of frames with a callback. | |
/// </summary> | |
/// <param name="delayedCall">The callback you want to invoke when the frame number is hit.</param> | |
/// <param name="frameCount">The number of frames you want to delay by.</param> | |
/// <returns>The id of the call.</returns> | |
public static ushort ByFrames(DelayedDelegate delayedCall, int frameCount) | |
{ | |
return AllocateCall(delayedCall, DelayType.FrameNumber, 0f, Time.frameCount + frameCount, null, false, 0); | |
} | |
/// <summary> | |
/// Delays a function call by a set number of frames with a callback. | |
/// </summary> | |
/// <typeparam name="T">The parameter type of the function you want to call.</typeparam> | |
/// <param name="delayedCall">The callback you want to invoke when the frame number is hit.</param> | |
/// <param name="frameCount">The number of frames you want to delay by.</param> | |
/// <param name="argument">The argument of the function you want to invoke.</param> | |
/// <returns>The id of the call.</returns> | |
public static ushort ByFrames<T>(DelayedDelegate<T> delayedCall, int frameCount, T argument) | |
{ | |
return AllocateCall(delayedCall, DelayType.FrameNumber, 0f, Time.frameCount + frameCount, argument, true, 0); | |
} | |
/// <summary> | |
/// Takes a function and invokes it a set amount of seconds later. | |
/// </summary> | |
/// <param name="delayedCall">The function you want to delay.</param> | |
/// <param name="seconds">The number of seconds later you want to delay the call.</param> | |
/// <returns>The ID of the delayed function. Used to cancel it with <see cref="CancelCall"/></returns> | |
public static ushort BySeconds(DelayedDelegate delayedCall, float seconds) | |
{ | |
return AllocateCall(delayedCall, DelayType.Seconds, seconds, 0, null, false, 0); | |
} | |
/// <summary> | |
/// Takes a function and invokes it a set amount of seconds later with one parameter. | |
/// </summary> | |
/// <param name="delayedCall">The function you want to delay.</param> | |
/// <param name="seconds">The number of seconds later you want to delay the call.</param> | |
/// <param name="argument">The argument you want to send when we invoke the function.</param> | |
/// <returns>The ID of the delayed function. Used to cancel it with <see cref="CancelCall"/></returns> | |
public static ushort BySeconds<T>(DelayedDelegate<T> delayedCall, float seconds, T argument) | |
{ | |
return AllocateCall(delayedCall, DelayType.Seconds, seconds, 0, argument, true, 0); | |
} | |
/// <summary> | |
/// Cancels a call based off an id. The ID is given to you when using the return value from | |
/// <see cref="BySeconds"/> or <see cref="ToNextFrame"/>. | |
/// </summary> | |
/// <param name="id">The ID of the call you want to cancel.</param> | |
/// <returns>True if a call was canceled and false if it was not.</returns> | |
public static bool CancelCall(ushort id) | |
{ | |
return false; | |
} | |
/// <summary> | |
/// Loops over array of delayed calls and tries to find one that is not allocated. If none is found the array is resized and invoked again. | |
/// If an instance is found it's set and allocated. | |
/// </summary> | |
/// <param name="callback">The function you want to call</param> | |
/// <param name="type">The type of callback it has</param> | |
/// <param name="seconds">The number of seconds you want to delay if the type is <see cref="DelayType.Seconds"/></param> | |
/// <param name="frame">The frame number you want to invoke this at if the type is <see cref="DelayType.FrameNumber"/></param> | |
/// <param name="argument">An argument to send to the function only if hasArugment is true.></param> | |
/// <param name="hasArgument">If we have an argument or not.</param> | |
/// <param name="startingIndex">The starting index we look for unused DelayedCallas at. Used for when the array is resized.</param> | |
/// <returns>The ID of the call.</returns> | |
private static ushort AllocateCall(Delegate callback, DelayType type, float seconds, int frame, object argument, bool hasArgument, int startingIndex = 0) | |
{ | |
DelayedCall call; | |
for (int i = startingIndex; i < _delayedCalls.Length; i++) | |
{ | |
call = _delayedCalls[i]; | |
if (call.delayType == DelayType.NotAllocated) | |
{ | |
if (type == DelayType.FrameNumber) | |
{ | |
call.SetToFrames(callback, frame, argument, hasArgument); | |
_delayedCalls[i] = call; | |
SubscribeToUpdate(); | |
return call.ID; | |
} | |
else if (type == DelayType.Seconds) | |
{ | |
call.SetToSeconds(callback, seconds, argument, hasArgument); | |
_delayedCalls[i] = call; | |
SubscribeToUpdate(); | |
return call.ID; | |
} | |
} | |
} | |
// We did not find any unused calls so we have to add some more | |
int arraySize = _delayedCalls.Length; | |
// Resize it | |
Array.Resize(ref _delayedCalls, arraySize + POOL_GROWTH_STEP_SIZE); | |
// Loop over and initialize them | |
for (int i = arraySize; i < arraySize + POOL_GROWTH_STEP_SIZE; i++) | |
{ | |
_delayedCalls[i] = new DelayedCall(_nextCallID); | |
_nextCallID++; | |
} | |
// Invoke the function again to get the index | |
return AllocateCall(callback, type, seconds, frame, argument, hasArgument, arraySize - 1); | |
} | |
/// <summary> | |
/// Subscribes to update if we are not already. | |
/// </summary> | |
private static void SubscribeToUpdate() | |
{ | |
if (!_isSubscribedToUpdate) | |
{ | |
UpdateRunner.onUpdate += Update; | |
_isSubscribedToUpdate = true; | |
} | |
} | |
/// <summary> | |
/// Invoked every frame by <see cref="UpdateRunner"/> when we have any Delayed Calls | |
/// in our array that are active. | |
/// </summary> | |
private static void Update() | |
{ | |
// Reset out flag telling us to unsubscribe from update | |
_shouldStaySubscribedToUpdate = false; | |
// Loop over all elements | |
for (int i = _delayedCalls.Length - 1; i >= 0; i--) | |
{ | |
// Get our call | |
DelayedCall call = _delayedCalls[i]; | |
// Tick it and if any return true we should stay subscribed | |
_shouldStaySubscribedToUpdate |= call.Tick(); | |
// Set it back to our array | |
_delayedCalls[i] = call; | |
} | |
// Check if we should unsubscribe. | |
if (!_shouldStaySubscribedToUpdate) | |
{ | |
UpdateRunner.onUpdate -= Update; | |
_isSubscribedToUpdate = false; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using JetBrains.Annotations; | |
using UnityEngine.Assertions; | |
/// <summary> | |
/// Allows any class to subscribe to Update callbacks using | |
/// the static delegate. | |
/// </summary> | |
public sealed class UpdateRunner : MonoBehaviour | |
{ | |
/// <summary> | |
/// The delegate we use to subscribe update functions. | |
/// </summary> | |
public delegate void UpdateDelegate(); | |
/// <summary> | |
/// Our delegate that we use to invoke our callbacks | |
/// </summary> | |
private static UpdateDelegate m_OnUpdate; | |
#if UNITY_ASSERTIONS | |
/// <summary> | |
/// A flag used to make sure we only have one instance created. | |
/// </summary> | |
private static bool m_HasInstance; | |
#endif | |
/// <summary> | |
/// Adds or removes a method that will be invoked on every | |
/// Unity update call. | |
/// </summary> | |
public static event UpdateDelegate onUpdate | |
{ | |
add { m_OnUpdate += value; } | |
remove { m_OnUpdate -= value; } | |
} | |
/// <summary> | |
/// Creates a new instance of the Update Runner before the first scene is loaded. | |
/// </summary> | |
[UsedImplicitly] | |
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] | |
private static void Initialize() | |
{ | |
// Create a Game Object in the scene | |
GameObject gameObject = new GameObject("[Update Runner]"); | |
// Add our component | |
UpdateRunner runner = gameObject.AddComponent<UpdateRunner>(); | |
// Mark it so it does not get destroyed | |
DontDestroyOnLoad(gameObject); | |
// Hide our runner if we are not in debug mode. | |
gameObject.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector; | |
} | |
#if UNITY_ASSERTIONS | |
/// <summary> | |
/// Sets our flag that we have an instance. If the flag is already | |
/// set we destroy the newly created one to stop future bugs. | |
/// </summary> | |
private void Awake() | |
{ | |
Assert.IsFalse(m_HasInstance, "A second instance of Update Runner was created. There should only be one instance or update will be called more then onces per frame"); | |
// Set our instance flag | |
m_HasInstance = true; | |
} | |
/// <summary> | |
/// Sets the flag that we don't have an instance when | |
/// this is destroyed. | |
/// </summary> | |
private void OnDestroy() | |
{ | |
m_HasInstance = false; | |
} | |
#endif | |
/// <summary> | |
/// Invoked by Unity every frame this version is unsafe as in if | |
/// we get an exception all update calls will be canceled. | |
/// </summary> | |
[UsedImplicitly] | |
private void Update() | |
{ | |
if(m_OnUpdate != null) | |
{ | |
m_OnUpdate(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment