Skip to content

Instantly share code, notes, and snippets.

@geniikw
Last active October 18, 2022 08:00
Show Gist options
  • Save geniikw/071463c491eee975c863a9163c9dcf69 to your computer and use it in GitHub Desktop.
Save geniikw/071463c491eee975c863a9163c9dcf69 to your computer and use it in GitHub Desktop.
Unity3d, CoroutineChain
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using geniikw.CChain;
using System;
using Object = UnityEngine.Object;
#region Accessor
/*
CoroutineChain (v0.2.1) author - [email protected]
all free, no duty, no warranty.
If you feel good, you can apply MIT license(this is not enforceable)
*/
public enum ELogType
{
NORMAL,
WARRNING,
ERROR
}
public static class MonobehaviourExtend
{
public static ChainBase StartChain(this MonoBehaviour mono)
{
return ChainBase.BasePool.Spawn(mono);
}
}
/// <summary>
/// 호환성을 위해 이름유지.
/// </summary>
public static class CoroutineChain
{
class Dispather : MonoBehaviour { }
static Dispather m_instance;
static Dispather Instance
{
get
{
if (m_instance == null)
{
m_instance = new GameObject("CoroutineChain").AddComponent<Dispather>();
Object.DontDestroyOnLoad(m_instance);
}
return m_instance;
}
}
public static void StopAll()
{
m_instance.StopAllCoroutines();
}
public static ChainBase Start
{
get
{
return ChainBase.BasePool.Spawn(Instance);
}
}
}
#endregion
#region Util
namespace geniikw.CChain
{
public class MemoryPool<T, TParam> where T : new()
{
Stack<T> m_pool = new Stack<T>();
private readonly Action<T, TParam> _OnSpawn;
private readonly Action<T> _OnDespawn;
public MemoryPool(Action<T, TParam> OnSpawn = null, Action<T> OnDespawn = null)
{
_OnDespawn = OnDespawn;
_OnSpawn = OnSpawn;
}
public T Spawn(TParam init)
{
T item;
if (m_pool.Count == 0)
{
item = new T();
}
else
{
item = m_pool.Pop();
}
if (_OnSpawn != null)
_OnSpawn(item, init);
return item;
}
public void Despawn(T item)
{
if (_OnDespawn != null)
_OnDespawn(item);
m_pool.Push(item);
}
}
public class MemoryPool<T> where T : new()
{
Stack<T> m_pool = new Stack<T>();
private readonly Action<T> _OnSpawn;
private readonly Action<T> _OnDespawn;
public MemoryPool(Action<T> OnSpawn = null, Action<T> OnDespawn = null)
{
_OnDespawn = OnDespawn;
_OnSpawn = OnSpawn;
}
public T Spawn()
{
T item;
if (m_pool.Count == 0)
{
item = new T();
}
else
{
item = m_pool.Pop();
}
if (_OnSpawn != null)
_OnSpawn(item);
return item;
}
public void Despawn(T item)
{
if (_OnDespawn != null)
_OnDespawn(item);
m_pool.Push(item);
}
}
#endregion
#region CChainInternal
public class Chain
{
EType type;
MonoBehaviour player;
IEnumerator routine;
IEnumerator[] parallelRoutine;
Action action;
public Coroutine Play()
{
switch (type)
{
default:
case EType.NonCoroutine:
action();
return null;
case EType.Parallel:
return player.StartCoroutine(Parallel(parallelRoutine));
case EType.Single:
return player.StartCoroutine(routine);
}
}
public Chain SetupRoutine(IEnumerator routine, MonoBehaviour player)
{
type = EType.Single;
this.player = player;
this.routine = routine;
return this;
}
public Chain SetupParallel(IEnumerator[] routines, MonoBehaviour player)
{
type = EType.Parallel;
this.player = player;
this.parallelRoutine = routines;
return this;
}
public Chain SetupNon(System.Action action, MonoBehaviour player)
{
type = EType.NonCoroutine;
this.player = player;
this.action = action;
return this;
}
public void Clear()
{
player = null;
routine = null;
action = null;
parallelRoutine = null;
}
IEnumerator Parallel(IEnumerator[] routines)
{
var all = 0;
foreach (var r in routines)
all++;
var c = 0;
foreach (var r in routines)
player.StartChain()
.Play(r)
.Call(() => c++);
while (c < all)
yield return null;
}
public enum EType
{
Single,
Parallel,
NonCoroutine
}
}
public interface IChain
{
Coroutine Play(MonoBehaviour mono);
}
public class ChainBase : CustomYieldInstruction
{
public static MemoryPool<ChainBase, MonoBehaviour> BasePool = new MemoryPool<ChainBase, MonoBehaviour>((c, m) => c.Setup(m), c => c.Clear());
public static MemoryPool<Chain> ChainPool = new MemoryPool<Chain>(null, c => c.Clear());
MonoBehaviour _player;
Queue<Chain> m_chainQueue = new Queue<Chain>();
bool m_isPlay = true;
public override bool keepWaiting
{
get
{
return m_isPlay;
}
}
ChainBase Setup(MonoBehaviour player)
{
m_isPlay = true;
_player = player;
_player.StartCoroutine(Routine());
return this;
}
void Clear()
{
_player = null;
m_chainQueue.Clear();
}
IEnumerator Routine()
{
yield return null;
while (m_chainQueue.Count > 0)
{
var chain = m_chainQueue.Dequeue();
var cr = chain.Play();
if (cr != null)
yield return cr;
ChainPool.Despawn(chain);
}
m_isPlay = false;
BasePool.Despawn(this);
}
public ChainBase Play(IEnumerator routine)
{
m_chainQueue.Enqueue(ChainPool.Spawn().SetupRoutine(routine, _player));
return this;
}
public ChainBase Wait(float waitSec)
{
m_chainQueue.Enqueue(ChainPool.Spawn().SetupRoutine(WaitRoutine(waitSec), _player));
return this;
}
public ChainBase Parallel(params IEnumerator[] routines)
{
m_chainQueue.Enqueue(ChainPool.Spawn().SetupParallel(routines, _player));
return this;
}
public ChainBase Sequential(params IEnumerator[] routines)
{
foreach (var routine in routines)
m_chainQueue.Enqueue(ChainPool.Spawn().SetupRoutine(routine, _player));
return this;
}
public ChainBase Log(string log, ELogType type = ELogType.NORMAL)
{
Action action;
switch (type)
{
default:
case ELogType.NORMAL:
action = () => Debug.Log(log); break;
case ELogType.WARRNING:
action = () => Debug.LogWarning(log); break;
case ELogType.ERROR:
action = () => Debug.LogError(log); break;
}
m_chainQueue.Enqueue(ChainPool.Spawn().SetupNon(action, _player));
return this;
}
public ChainBase Call(Action action)
{
m_chainQueue.Enqueue(ChainPool.Spawn().SetupNon(action, _player));
return this;
}
IEnumerator WaitRoutine(float wait)
{
yield return new WaitForSeconds(wait);
}
}
}
#endregion
public class Example{
void Start(){
Func<IEnumerator> wait4sec = ()=>WaitSec(4);
var wait5sec = WaitSec(5);
CoroutineChain.Start
.Play(Test1())
.Call(() => Debug.Log("Middle of chaining"))//non coroutine
.Wait(1f)
.Sequential(Test1, Test2, wait4sec) //runs one after another1
.Call(() => Debug.Log("Sequential End"))
.Parallel(Test1(), Test2(), WaitSec(3)) //runs all at the same time
.Call(() => Debug.Log("Parallel End!"));
}
int count = 0;
IEnumerator WaitSec(float sec)
{
yield return new WaitForSeconds(sec);
Debug.Log("WaitSec Complete! count:" + count++);
}
IEnumerator Test1()
{
yield return new WaitForSeconds(1);
Debug.Log("Test1 Complete! count:" + count++);
}
IEnumerator Test2()
{
yield return new WaitForSeconds(1);
Debug.Log("Test2 Complete! count:" + count++);
}
}
  • add Call(func) method.
  • add Start static property.

  • rename Plll to Parrallel

  • remove UniRx dependency.
  • add Play(Func next)
  • add Sequencial
  • rename Parallel to PlayWithUpper and change Parallel

  • remove PlayWithUpper
  • make Parallel completely?

release 0.1;

  • chain inherit CustomYieldInstruction Now can this [yield return CoroutineChain.Start.Play(Test1)];

version 0.1.1

  • change List to IEnumerable in parrallel
  • add Log chain

version 0.2

  • apply memorypool
  • optimization
@geniikw
Copy link
Author

geniikw commented Jan 23, 2018

todo:

  1. 무조건 Dispather를 쓰는 것 대신 각각의 Monobehaviour에서도 불러낼 수 있도록
  2. Coroutine을 UniRx의 MicroCoroutine처럼 만들기
  3. Func를 사용할 경우 반복적으로 불러낼 때 이슈가 생김
  4. UnitTest 방식 생각하기

  1. Queue를 만들어서 체인이 끝나면 큐에서 하나씩 꺼내서 실행하도록 바꿔보기

@geniikw
Copy link
Author

geniikw commented Feb 16, 2018

에셋스토어 런칭으로 저장소로 이전했습니다.
중요해서 두번 말합니다. 저장소로 이전했습니다.

https://github.com/geniikw/CoroutineChain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment