├── ListPool.cs ├── ObjectPool.cs ├── README.md └── TaskManager.cs /ListPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace TexDrawLib 6 | { 7 | //Main Class Stack Manager 8 | public static class ListPool 9 | { 10 | // Object pool to avoid allocations. 11 | private static readonly ObjectPool> s_ListPool = new ObjectPool>(); 12 | 13 | public static List Get() 14 | { 15 | return s_ListPool.Get(); 16 | } 17 | 18 | public static void Release(List toRelease) 19 | { 20 | if(toRelease.Count > 0 && toRelease[0] is IFlushable) 21 | { 22 | for (int i = 0; i < toRelease.Count; i++) 23 | { 24 | IFlushable obj = (IFlushable)toRelease[i]; 25 | (obj).Flush(); 26 | } 27 | } 28 | toRelease.Clear(); 29 | s_ListPool.Release(toRelease); 30 | } 31 | 32 | //Advanced purposes only 33 | public static void ReleaseNoFlush(List toRelease) 34 | { 35 | toRelease.Clear(); 36 | s_ListPool.Release(toRelease); 37 | } 38 | } 39 | 40 | internal static class ObjPool where T : class, IFlushable, new() 41 | { 42 | // Object pool to avoid allocations. 43 | private static readonly ObjectPool s_ObjPool = new ObjectPool(); 44 | 45 | public static T Get() 46 | { 47 | T obj = s_ObjPool.Get(); 48 | obj.SetFlushed(false); 49 | return obj; 50 | } 51 | 52 | public static void Release(T toRelease) 53 | { 54 | if(toRelease.GetFlushed()) 55 | return; 56 | toRelease.SetFlushed(true); 57 | s_ObjPool.Release(toRelease); 58 | } 59 | } 60 | 61 | //Interface to get a class to be flushable (flush means to be released to the main class stack 62 | //when it's unused, later if code need a new instance, the main stack will give this class back 63 | //instead of creating a new instance (which later introducing Memory Garbages)). 64 | internal interface IFlushable 65 | { 66 | bool GetFlushed(); 67 | 68 | void SetFlushed(bool flushed); 69 | 70 | void Flush(); 71 | } 72 | } -------------------------------------------------------------------------------- /ObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine.Events; 3 | using UnityEngine; 4 | 5 | namespace TexDrawLib 6 | { 7 | public class ObjectPool where T : new () 8 | { 9 | private readonly Stack m_Stack = new Stack(); 10 | 11 | #if UNITY_EDITOR 12 | public int countAll { get; private set; } 13 | public int countActive { get { return countAll - countInactive; } } 14 | public int countInactive { get { return m_Stack.Count; } } 15 | #endif 16 | 17 | public T Get() 18 | { 19 | T element; 20 | if (m_Stack.Count == 0) 21 | { 22 | element = new T(); 23 | #if UNITY_EDITOR 24 | countAll++; 25 | // Debug.LogFormat( "Pop New {0}, Total {1}", typeof(T).Name, countAll); 26 | #endif 27 | } 28 | else 29 | { 30 | element = m_Stack.Pop(); 31 | } 32 | return element; 33 | } 34 | 35 | public void Release(T element) 36 | { 37 | #if UNITY_EDITOR 38 | if (m_Stack.Count > 0 && ReferenceEquals(m_Stack.Peek(), element)) 39 | Debug.LogError("Internal error. Trying to destroy object that is already released to pool."); 40 | #endif 41 | m_Stack.Push(element); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TaskManager 2 | A Futher implementation of Coroutines in Unity3D 3 | 4 | This implementation is a derived implementation from 5 | https://github.com/krockot/Unity-TaskManager 6 | 7 | Instead of these codes: 8 | 9 | ```C# 10 | void Start() { 11 | StartCoroutine(Loop20Sec()); 12 | } 13 | 14 | void OnDisable() { 15 | StopCoroutine(Loop20Sec()); 16 | } 17 | 18 | IEnumerator Loop20Sec() { 19 | float tim = Time.time + 20; 20 | while (tim > Time.time) { 21 | float progress = 1 - ((tim - Time.time) / totalTim); 22 | //Moving from (0,0,0) to (1,0,0) 23 | transform.position = new Vector3(progress,0,0); 24 | } 25 | } 26 | ``` 27 | 28 | why don't just wrap up that coroutine to a single code? 29 | 30 | ```C# 31 | Task c; 32 | void Start() { 33 | //This make life just get more simpler to tweening things 34 | c = Task.Get(delegate(float t) { transform.position = new Vector3(t,0,0); }, 20); 35 | } 36 | 37 | void OnDisable() { 38 | c.Stop(); 39 | } 40 | ``` 41 | not just using delegates, this Task also support pause/resume system, And better handling for specific Task, 42 | Which is a feature from the base code (https://github.com/krockot/Unity-TaskManager). Additionally, when it's 43 | got unused, it's resources (memory) will automatically kept for later usage, this implementation based from my 44 | [TEXDraw](http://u3d.as/mFe) package. This also means that task will performs ss fast as possible, without 45 | suffer from GC Allocations. 46 | 47 | See sources for more example and documentation. 48 | -------------------------------------------------------------------------------- /TaskManager.cs: -------------------------------------------------------------------------------- 1 | /// TaskManager.cs 2 | /// Copyright (c) 2011, Ken Rockot . All rights reserved. 3 | /// Everyone is granted non-exclusive license to do anything at all with this code. 4 | /// 5 | /// 5 years later, the derivation from this code, done by Wildan Mubarok 6 | /// Everyone is still granted non-exclusive license to do anything at all with this code. 7 | /// 8 | /// This is a new coroutine interface for Unity. 9 | /// 10 | /// The motivation for this is twofold: 11 | /// 12 | /// 1. The existing coroutine API provides no means of stopping specific 13 | /// coroutines; StopCoroutine only takes a string argument, and it stops 14 | /// all coroutines started with that same string; there is no way to stop 15 | /// coroutines which were started directly from an enumerator. This is 16 | /// not robust enough and is also probably pretty inefficient. 17 | /// 18 | /// 2. StartCoroutine and friends are MonoBehaviour methods. This means 19 | /// that in order to start a coroutine, a user typically must have some 20 | /// component reference handy. There are legitimate cases where such a 21 | /// constraint is inconvenient. This implementation hides that 22 | /// constraint from the user. 23 | /// 24 | /// And another two benefit by using my derivation code including: 25 | /// 26 | /// 1. It's Garbage-Collection free; the original workflow uses New() keyword 27 | /// for every new Coroutiones, which means that after that coroutine finished, 28 | /// This class isn't get recycled, resulting a new GC allocation. This also means 29 | /// that by using this derived-code, resulting an increase in performance 30 | /// on both editor and real devices. 31 | /// 32 | /// 2. the Implementation by IEnumerable isn't efficient enough if it used for many times. 33 | /// Most of the time, we use Corountines for tweening a parameter. And to create one, 34 | /// Takes a time to think the math behind the scene. This is C#, and there's why delegates 35 | /// exist. With this code, You can implement Delegate (especially it's ground breaking feature, 36 | /// Anonymous method, google for that) to wrap up several lines to just a single line, while 37 | /// this class handle the math for the time function. See below for futher example. 38 | /// 39 | /// Example usage: 40 | /// 41 | /// ---------------------------------------------------------------------------- 42 | /// IEnumerator MyAwesomeTask() 43 | /// { 44 | /// while(true) { 45 | /// Debug.Log("Logcat iz in ur consolez, spammin u wif messagez."); 46 | /// yield return null; 47 | //// } 48 | /// } 49 | /// 50 | /// IEnumerator TaskKiller(float delay, Task t) 51 | /// { 52 | /// yield return new WaitForSeconds(delay); 53 | /// t.Stop(); 54 | /// } 55 | /// 56 | /// void SomeCodeThatCouldBeAnywhereInTheUniverse() 57 | /// { 58 | /// Task spam = new Task(MyAwesomeTask()); 59 | /// new Task(TaskKiller(5, spam)); 60 | /// } 61 | /// ---------------------------------------------------------------------------- 62 | /// 63 | /// When SomeCodeThatCouldBeAnywhereInTheUniverse is called, the debug console 64 | /// will be spammed with annoying messages for 5 seconds. 65 | /// 66 | /// Simple, really. There is no need to initialize or even refer to TaskManager. 67 | /// When the first Task is created in an application, a "TaskManager" GameObject 68 | /// will automatically be added to the scene root with the TaskManager component 69 | /// attached. This component will be responsible for dispatching all coroutines 70 | /// behind the scenes. 71 | /// 72 | /// Task also provides an event that is triggered when the coroutine exits. 73 | /// 74 | /// ---------------------------------------------------------------------------- 75 | /// 76 | /// And as a said before, constant use of New() keyword will result on a significant 77 | /// GC Allocations. this problem can be addressed by reusing the class after it's 78 | /// current Coroutine finished. This is somewhat complex in theory, but, don't worry, 79 | /// Here we use the implemention of a Class Pooler from what I'm use for my TEXDraw package. 80 | /// To make the optimization takes effect, just replace from "new Task" to "Task.Get": 81 | /// 82 | /// void SomeCodeThatCouldBeAnywhereInTheUniverse() 83 | /// { 84 | /// Task spam = Task.Get(MyAwesomeTask()); 85 | /// Task.Get(TaskKiller(5, spam)); 86 | /// } 87 | 88 | using UnityEngine; 89 | using System.Collections; 90 | using System.Collections.Generic; 91 | using TexDrawLib; 92 | 93 | /// A Task object represents a coroutine. Tasks can be started, paused, and stopped. 94 | /// It is an error to attempt to start a task that has been stopped or which has 95 | /// naturally terminated. 96 | public class Task : IFlushable 97 | { 98 | /// Returns true if and only if the coroutine is running. Paused tasks 99 | /// are considered to be running. 100 | public bool Running { 101 | get { 102 | return task.Running; 103 | } 104 | } 105 | 106 | /// Returns true if and only if the coroutine is currently paused. 107 | public bool Paused { 108 | get { 109 | return task.Paused; 110 | } 111 | } 112 | 113 | /// Determine whether this task is used once, or false if not 114 | /// If set to false, then it's your responsibility to call Flush() if this class is no longer use. 115 | public bool flushAtFinish = true; 116 | 117 | /// Delegate for termination subscribers. manual is true if and only if 118 | /// the coroutine was stopped with an explicit call to Stop(). 119 | public delegate void FinishedHandler (bool manual); 120 | 121 | /// Termination event. Triggered when the coroutine completes execution. 122 | public event FinishedHandler Finished; 123 | 124 | 125 | /// Begins execution of the coroutine 126 | public void Start () 127 | { 128 | /*if(Running) 129 | task.Restart(); 130 | else*/ 131 | task.Start (); 132 | } 133 | 134 | /// Discontinues execution of the coroutine at its next yield. 135 | public void Stop () 136 | { 137 | task.Stop (); 138 | } 139 | 140 | public void Pause () 141 | { 142 | task.Pause (); 143 | } 144 | 145 | public void Unpause () 146 | { 147 | task.Unpause (); 148 | } 149 | 150 | public void Reset () 151 | { 152 | task.Restart (); 153 | } 154 | 155 | void TaskFinished (bool manual) 156 | { 157 | FinishedHandler handler = Finished; 158 | if (handler != null) 159 | handler (manual); 160 | if (flushAtFinish) 161 | Flush (); 162 | } 163 | 164 | TaskManager.TaskState task; 165 | 166 | 167 | 168 | /// Creates a new Task object for the given coroutine. 169 | /// 170 | /// If autoStart is true (default) the task is automatically started 171 | /// upon construction. 172 | 173 | public Task() 174 | {} 175 | 176 | /// Don't use this, use Task.Get instead to be able get from 177 | /// unused task, Preveting futher GC Allocates 178 | public Task (IEnumerator c, bool autoStart = true) 179 | { 180 | task = TaskManager.CreateTask (c); 181 | task.Finished += TaskFinished; 182 | if (autoStart) 183 | Start (); 184 | } 185 | 186 | /// Initialize new Task, or get from unused stack 187 | public static Task Get (IEnumerator c, bool autoStart = true) 188 | { 189 | Task t = ObjPool.Get (); 190 | if (t.task == null) 191 | t.task = TaskManager.CreateTask (c); 192 | else 193 | t.task.coroutine = c; 194 | t.task.Finished += t.TaskFinished; 195 | if (autoStart) 196 | t.Start (); 197 | return t; 198 | } 199 | 200 | /// Delegate variant, for the simplicity of a sake 201 | /// Using linear interpolation 202 | public static Task Get (CallBack c, float totalTime, bool autoStart = true) 203 | { 204 | return Get (Iterator (c, totalTime)); 205 | } 206 | 207 | /// Delegate variant, for the simplicity of a sake 208 | /// Customize your own interpolation type 209 | public static Task Get (CallBack c, float totalTime, InterpolationType interpolType, bool inverted = false, bool autoStart = true) 210 | { 211 | return Get (Iterator (c, totalTime, interpolType, inverted)); 212 | } 213 | 214 | public delegate void CallBack (float t); 215 | 216 | static IEnumerator Iterator (CallBack call, float totalTim) 217 | { 218 | float tim = Time.time + totalTim; 219 | while (tim > Time.time) { 220 | //The time that returns is always normalized between 0...1 221 | call (1 - ((tim - Time.time) / totalTim)); 222 | yield return null; 223 | } 224 | //When it's done, make sure we complete the time with perfect 1 225 | call (1f); 226 | } 227 | 228 | static IEnumerator Iterator (CallBack call, float totalTime, InterpolationType interpolType, bool inverted = false) 229 | { 230 | float tim = Time.time + totalTime; 231 | while (tim > Time.time) { 232 | float t = 1 - ((tim - Time.time) / totalTime); 233 | switch (interpolType) { 234 | case InterpolationType.Linear: break; 235 | case InterpolationType.SmoothStep: t = t*t*(3f - 2f*t); break; 236 | case InterpolationType.SmootherStep: t = t*t*t*(t*(6f*t - 15f) + 10f); break; 237 | case InterpolationType.Sinerp: t = Mathf.Sin(t * Mathf.PI / 2f); break; 238 | case InterpolationType.Coserp: t = 1-Mathf.Cos(t * Mathf.PI / 2f); break; 239 | case InterpolationType.Square: t = Mathf.Sqrt(t); break; 240 | case InterpolationType.Quadratic: t = t * t; break; 241 | case InterpolationType.Cubic: t = t*t*t; break; 242 | case InterpolationType.CircularStart: t = Mathf.Sqrt(2*t+t*t); break; 243 | case InterpolationType.CircularEnd: t = 1-Mathf.Sqrt(1-t*t); break; 244 | case InterpolationType.Random: t = Random.value; break; 245 | case InterpolationType.RandomConstrained: t = Mathf.Max(Random.value, t); break; 246 | } 247 | if(inverted) 248 | t = 1 - t; 249 | //The time that returns is always normalized between 0...1 250 | call (t); 251 | yield return null; 252 | } 253 | //When it's done, make sure we complete the time with perfect 1 (or 0) 254 | call (inverted ? 0f : 1f); 255 | } 256 | 257 | //Optimizer stuff --------- 258 | bool m_flushed; 259 | 260 | public bool GetFlushed () 261 | { 262 | return m_flushed; 263 | } 264 | 265 | public void SetFlushed (bool flushed) 266 | { 267 | m_flushed = flushed; 268 | } 269 | 270 | public void Flush () 271 | { 272 | task.Stop (); 273 | task.Finished -= TaskFinished; 274 | ObjPool.Release (this); 275 | } 276 | 277 | //Task with ID functionality, prevent duplicate Coroutines being run 278 | 279 | static Dictionary idStack = new Dictionary(); 280 | 281 | /// Delegate variant, for the simplicity of a sake 282 | /// Using linear interpolation 283 | /// Use Id for prevent duplicate coroutines 284 | public static Task Get (IEnumerator c, string id, bool overrideIfExist = true, bool autoStart = true) 285 | { 286 | if(idStack.ContainsKey(id)) 287 | { 288 | Task t = idStack[id]; 289 | if(overrideIfExist) 290 | { 291 | if(c == t.task.coroutine) 292 | t.task.Restart(); 293 | else 294 | { 295 | t.Stop(); 296 | t.task.coroutine = c; 297 | } 298 | } 299 | if(autoStart) 300 | t.Start(); 301 | return t; 302 | } else { 303 | Task t = Get(c, autoStart); 304 | idStack.Add(id, t); 305 | return t; 306 | } 307 | } 308 | 309 | /// Delegate variant, for the simplicity of a sake 310 | /// Using linear interpolation 311 | /// Use Id for prevent duplicate coroutines 312 | public static Task Get (CallBack c, float totalTime, string id, bool overrideIfExist = true, bool autoStart = true) 313 | { 314 | return Get(Iterator (c, totalTime), id, autoStart); 315 | } 316 | 317 | /// Delegate variant, for the simplicity of a sake 318 | /// Customize your own interpolation type 319 | /// Use Id for prevent duplicate coroutines 320 | public static Task Get (CallBack c, float totalTime, string id, InterpolationType interpolType, 321 | bool inverted = false, bool overrideIfExist = true, bool autoStart = true) 322 | { 323 | return Get(Iterator (c, totalTime, interpolType, inverted), id, autoStart); 324 | } 325 | 326 | } 327 | 328 | class TaskManager : MonoBehaviour 329 | { 330 | public class TaskState 331 | { 332 | public bool Running { 333 | get { 334 | return running; 335 | } 336 | } 337 | 338 | public bool Paused { 339 | get { 340 | return paused; 341 | } 342 | } 343 | 344 | public delegate void FinishedHandler (bool manual); 345 | 346 | public event FinishedHandler Finished; 347 | 348 | public IEnumerator coroutine; 349 | bool running; 350 | bool paused; 351 | bool stopped; 352 | bool restart; 353 | 354 | public TaskState (IEnumerator c) 355 | { 356 | coroutine = c; 357 | } 358 | 359 | public void Pause () 360 | { 361 | paused = true; 362 | } 363 | 364 | public void Unpause () 365 | { 366 | paused = false; 367 | } 368 | 369 | public void Restart () 370 | { 371 | restart = true; 372 | } 373 | 374 | public void Start () 375 | { 376 | running = true; 377 | stopped = false; 378 | singleton.StartCoroutine (CallWrapper ()); 379 | } 380 | 381 | public void Stop () 382 | { 383 | stopped = true; 384 | running = false; 385 | } 386 | 387 | IEnumerator CallWrapper () 388 | { 389 | yield return null; 390 | IEnumerator e = coroutine; 391 | while (running) { 392 | if (paused) 393 | yield return null; 394 | else if(restart){ 395 | restart = false; 396 | if(e != null) 397 | e.Reset(); 398 | } 399 | else { 400 | if(e != coroutine) 401 | e = coroutine; 402 | if (e != null && e.MoveNext ()) { 403 | yield return e.Current; 404 | } else { 405 | running = false; 406 | } 407 | } 408 | } 409 | 410 | FinishedHandler handler = Finished; 411 | if (handler != null) 412 | handler (stopped); 413 | } 414 | } 415 | 416 | static TaskManager singleton; 417 | 418 | public static TaskState CreateTask (IEnumerator coroutine) 419 | { 420 | if (singleton == null) { 421 | GameObject go = new GameObject ("TaskManager"); 422 | singleton = go.AddComponent (); 423 | } 424 | return new TaskState (coroutine); 425 | } 426 | } 427 | 428 | public enum InterpolationType 429 | { 430 | /// Standard linear interpolation 431 | Linear = 0, 432 | /// Smooth fade interpolation 433 | SmoothStep = 1, 434 | /// Smoother fade interpolation than SmoothStep 435 | SmootherStep = 2, 436 | /// Sine interpolation, smoothing at the end 437 | Sinerp = 3, 438 | /// Cosine interpolation, smoothing at the start 439 | Coserp = 4, 440 | /// Extreme bend towards end, low speed at end 441 | Square = 5, 442 | /// Extreme bend toward start, high speed at end 443 | Quadratic = 6, 444 | /// Stronger bending than Quadratic 445 | Cubic = 7, 446 | /// Spherical interpolation, vertical speed at start 447 | CircularStart = 8, 448 | /// Spherical interpolation, vertical speed at end 449 | CircularEnd = 9, 450 | /// Pure Random interpolation 451 | Random = 10, 452 | /// Random interpolation with linear constraining at 0..1 453 | RandomConstrained = 11 454 | } --------------------------------------------------------------------------------