Skip to content

Instantly share code, notes, and snippets.

@drZool
Last active October 29, 2019 16:42
Show Gist options
  • Select an option

  • Save drZool/7f37aa2dec2b03bb6f135a9dbdb4f8a4 to your computer and use it in GitHub Desktop.

Select an option

Save drZool/7f37aa2dec2b03bb6f135a9dbdb4f8a4 to your computer and use it in GitHub Desktop.
Delay actions. Keeps all actions in a sorted list to minimize overhead.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
/// <summary>
///
/// Delay actions. Keeps all actions in a sorted list to minimize overhead.
///
/// </summary>
/// <usage>
///
/// DelayedAction.time.Delay( 0.2f, MyAction );
/// DelayedAction.unscaledTime.Delay( 0.3f, () => Debug.Log("Hello!") );
/// DelayedAction.frames.Delay( 1, () => Debug.Log("We waited one frame!") );
///
/// DelayedAction.time.At( 13.37f, WinGame );
/// </usage>
public class DelayedAction : MonoBehaviour
{
static GameObject CreateGhost (string name)
{
var go = new GameObject (name);
GameObject.DontDestroyOnLoad (go);
go.hideFlags = HideFlags.HideAndDontSave;
return go;
}
static DelayedActionTime _time;
public static DelayedActionTime time {
get {
if (_time == null)
_time = CreateGhost ("DelayedAction").AddComponent<DelayedActionTime> ();
return _time;
}
}
static DelayedActionUnscaledTime _unscaledTime;
public static DelayedActionUnscaledTime unscaledTime {
get {
if (_unscaledTime == null)
_unscaledTime = CreateGhost ("DelayedActionUnscaledTime").AddComponent<DelayedActionUnscaledTime> ();
return _unscaledTime;
}
}
static DelayedActionRealtimeSinceStartup _realtimeSinceStartup;
public static DelayedActionRealtimeSinceStartup realtimeSinceStartup {
get {
if (_realtimeSinceStartup == null)
_realtimeSinceStartup = CreateGhost ("DelayedActionRealtimeSinceStartup").AddComponent<DelayedActionRealtimeSinceStartup> ();
return _realtimeSinceStartup;
}
}
static DelayedActionFrames _frames;
public static DelayedActionFrames frames {
get {
if (_frames == null)
_frames = CreateGhost ("DelayedActionFrames").AddComponent<DelayedActionFrames> ();
return _frames;
}
}
/// <summary>
/// Creates the using custom time. You are responsible for this object! it does not go away by itself
/// </summary>
/// <returns>The using custom time.</returns>
/// <param name="timeProvider">Time provider.</param>
public static DelayedActionCustomTime CreateUsingCustomTime (Func <float> timeProvider)
{
var component = CreateGhost ("DelayedActionCustomTime").AddComponent<DelayedActionCustomTime> ();
component.timeProvider = timeProvider;
return component;
}
}
public struct TimedAction
{
public Action action;
public float time;
public int id;
public TimedAction (float time, Action action, int id)
{
this.time = time;
this.action = action;
this.id = id;
}
}
public class DelayedActionRealtimeSinceStartup : DelayedActionBase
{
public float now{ get { return Time.realtimeSinceStartup; } }
void Update ()
{
while (sortedActions.Count > 0 && sortedActions [0].time < Time.realtimeSinceStartup) {
var a = sortedActions [0];
sortedActions.RemoveAt (0);
a.action.Invoke ();
}
}
public int Delay (float delay, Action action)
{
return At (Time.realtimeSinceStartup + delay, action);
}
public override int At (float time, Action action)
{
if (time <= Time.realtimeSinceStartup) { //Execute directly
action.Invoke ();
uniqueId++; //Had to return a valid id even if not added to sortedActions
return uniqueId - 1;
}
return base.At (time, action);
}
}
public class DelayedActionTime : DelayedActionBase
{
public float now{ get { return Time.time; } }
void Update ()
{
while (sortedActions.Count > 0 && sortedActions [0].time < Time.time) {
var a = sortedActions [0];
sortedActions.RemoveAt (0);
a.action.Invoke ();
}
}
public int Delay (float delay, Action action)
{
return At (Time.time + delay, action);
}
public override int At (float time, Action action)
{
if (time <= Time.time) { //Execute directly
action.Invoke ();
uniqueId++; //Had to return a valid id even if not added to sortedActions
return uniqueId - 1;
}
return base.At (time, action);
}
}
public class DelayedActionUnscaledTime : DelayedActionBase
{
public float now{ get { return Time.unscaledTime; } }
void Update ()
{
while (sortedActions.Count > 0 && sortedActions [0].time < Time.unscaledTime) {
var a = sortedActions [0];
sortedActions.RemoveAt (0);
a.action.Invoke ();
}
}
public int Delay (float delay, Action action)
{
return At (Time.unscaledTime + delay, action);
}
public override int At (float time, Action action)
{
if (time <= Time.unscaledTime) { //Execute directly
action.Invoke ();
uniqueId++; //Had to return a valid id even if not added to sortedActions
return uniqueId - 1;
}
return base.At (time, action);
}
}
public class DelayedActionFrames : DelayedActionBase
{
public int now{ get { return Time.frameCount; } }
void Update ()
{
while (sortedActions.Count > 0 && (int)sortedActions [0].time <= Time.frameCount) {
var a = sortedActions [0];
sortedActions.RemoveAt (0);
a.action.Invoke ();
}
}
public int Delay (int delay, Action action)
{
return At (Time.frameCount + delay, action);
}
public override int At (float time, Action action)
{
return At ((int)time, action);
}
public int At (int frame, Action action)
{
if ((int)frame <= Time.frameCount) { //Execute directly
action.Invoke ();
uniqueId++; //Had to return a valid id even if not added to sortedActions
return uniqueId - 1;
}
return base.At (frame, action);
}
}
public class DelayedActionBase : MonoBehaviour
{
internal int uniqueId = int.MinValue;
internal List<TimedAction> sortedActions = new List<TimedAction> (32);
internal float lastTime { get; private set; }
public virtual int At (float time, Action action)
{
if (time < lastTime) { //Optimization, lastTime can be removed
for (int i = 0; i < sortedActions.Count; ++i) {
if (sortedActions [i].time > time) {
sortedActions.Insert (i, new TimedAction (time, action, uniqueId++));
return uniqueId - 1;
}
}
}
lastTime = time;
sortedActions.Add (new TimedAction (time, action, uniqueId++));
return uniqueId - 1;
}
public void Remove (int? id)
{
if (id.HasValue) {
Remove (id.Value);
}
}
public void Remove (int id)
{
for (int i = 0; i < sortedActions.Count; ++i) {
if (sortedActions [i].id == id) {
sortedActions.RemoveAt (i);
return;
}
}
}
public void RemoveAll(){
sortedActions.Clear ();
}
}
public class DelayedActionCustomTime : DelayedActionBase
{
static float stub ()
{
return 0;
}
public Func<float> timeProvider = stub;
//Set this to something useful at runtime.
public float now{ get { return timeProvider (); } }
void Update ()
{
while (sortedActions.Count > 0 && sortedActions [0].time < timeProvider ()) {
var a = sortedActions [0];
sortedActions.RemoveAt (0);
a.action.Invoke ();
}
}
public int Delay (float delay, Action action)
{
return At (timeProvider () + delay, action);
}
public override int At (float time, Action action)
{
if (time <= timeProvider ()) { //Execute directly
action.Invoke ();
uniqueId++; //Had to return a valid id even if not added to sortedActions
return uniqueId - 1;
}
return base.At (time, action);
}
}
public class DelayedTest : MonoBehaviour
{
void Start ()
{
float now = Time.time;
var t = DelayedAction.time;
t.At (10f, () => LogTime ("G 10"));
t.At (1f, () => LogTime ("C 1"));
t.At (3f, () => LogTime ("F 3"));
t.At (2f, () => LogTime ("E 2"));
t.At (1.5f, () => LogTime ("D at time 115"));
t.At (0.1f, () => LogTime ("B 0.1"));
int id = t.Delay (0.0f, () => LogTime ("A unremovable 0"));
t.Remove (id);
id = t.Delay (0.0000001f, () => LogTime ("A removable 0.0000001"));
t.Remove (id);
t.At ((int)Time.time + 1, SpawnDelay);
var u = DelayedAction.unscaledTime;
// u.At (1, () => Time.timeScale = 0f);
// u.At (1, () => LogTime ("--------- Timescale at " + Time.timeScale));
// u.At (2, () => LogTime ("Unscaled 2"));
// u.At (5, () => Time.timeScale = 2f);
// u.At (5, () => LogTime ("--------- Timescale at " + Time.timeScale));
// u.At (6, () => LogTime ("Unscaled 6"));
var r = DelayedAction.realtimeSinceStartup;
r.At (2, () => LogTime ("Realtime At 2"));
r.At (r.now + 1, () => LogTime ("Realtime At >now< + 1"));
var c = DelayedAction.CreateUsingCustomTime (GetMusicTime);
c.Delay (1, () => LogTime ("Custom time 1"));
t.Delay (0.1f, () => Heavy (3));
}
void Heavy (int left)
{
var t = DelayedAction.time;
if (left <= 0) {
t.At (t.lastTime, Done);
return;
}
LogTime ("Heavy add random delays " + countMax);
for (int i = 0; i < countMax; ++i) {
t.Delay (UnityEngine.Random.value * 5, Add);
}
t.Delay (2.5f, () => Heavy (left - 1));
}
void Done ()
{
LogTime ("Added " + count + "/" + countMax);
}
void Add ()
{
count++;
}
int countMax = 1000;
int count = 0;
float GetMusicTime ()
{
return 0;
}
void LogTime (string message = null)
{
Debug.LogFormat ("{3} | time {0:0.00} unscaled {1:0.00} realtime {2:0.00}", Time.time, Time.unscaledTime, Time.realtimeSinceStartup, message);
}
bool on;
void SpawnDelay ()
{
Debug.Log ("SpawnDelay " + Time.time);
on = !on;
if (on) {
DelayedAction.time.At ((int)Time.time + 1, SpawnDelay);
} else {
DelayedAction.time.Delay (1, SpawnDelay);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment