posted by REDFORCE 2018. 12. 19. 01:45

#02.JobRepeatManager - 01


이번 글에 작성할 내용은 개인적으로 유니티를 사용하면서 이런 스크립트가 있으면 편할 것 같은데? 라는 생각에 만들어 본 스크립트이다.


그 이름은 Job Repeat Manager !!!!!!!!!!!


개인적으로 만들어놓고 그래서 요놈이 효율적일까? 괜찮은가? 안전한가? 

라는 질문을 수 차례했으나 답은...나도 모르겠당...ㅎㅎ


그래도 올려두면 누군가는 쓰지 않을까? 라는 생각에 피드백도 받아볼 겸 적는다.

-------------------------------


JobRepeatManager는 글쓴이가 심심해서 만들어 본 유틸리티 성향의(?) 스크립트 입니다.

처음에 만들게 된 이유는 다음의 예로 들어보겠습니다.


목적 : 어떤 함수의 내용을 반복적으로 수행하게 하고 싶다
 예) public void MyFunction() { ... } 을 50 번만 2.0초 간격으로 수행하고 싶다.


  + 그러나 public bool isExecute;  라는 변수가 true 일때만 하고 싶다.

  + 만약 public bool isDrop; 이라는 변수가 true 면은 중간에 멈추고 싶다.

  + Invoke나 Coroutine을 쓸 수는 없는 상황이다.

  + 수행되고 있는 펑션의 상태를 Visual로 보고싶다.


위와 같은 상황에서...이걸 어떻게 해결 해야할까...라는 고민중에

결국 다음과 같은 조건을 충족하도록 만들어본 유틸리티 코드입니다.


 조 건 

  1. Update() 를 쓰고 싶지는 않다.
  2. 어떤 내용(To do)을 n번 실행하게 하고 싶다.
  3. 수행되는 내용을 GameObject 로 관리하고 싶다.
  4. 람다(Lambda) 또는 Delegate를 이용한 Event 형태의 Job을 만들어서 관리하고 싶다.


결과적으로 만든 JobRepeatManager 는 아래의 2가지 클래스 파일로 구성 되어 있습니다.


본문에서는 Gist 코드만 남기고 설명은 다음글로 넘기겠습니다.


1) JobRepeatBase.cs


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
#if UNITY_EDITOR
using UnityEditor;
namespace AT.JobManager
{
[CustomEditor(typeof(JobRepeatBase))]
public class JobBaseEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
JobRepeatBase job = (JobRepeatBase)target;
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Execute Immediately"))
{
if (job.jobTodo != null)
{
job.jobTodo();
job.excuteCount++;
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Drop"))
{
job.state = JobRepeatBase.JOB_STATE.JOB_DROP;
}
if(GUILayout.Button("Drop Immediately"))
{
JobRepeatManager.Instance.RemoveJob(job.key);
DestroyImmediate(job.gameObject);
}
EditorGUILayout.EndHorizontal();
}
}
}
#endif
namespace AT.JobManager
{
[System.Serializable]
public class JobRepeatBase : MonoBehaviour {
public enum JOB_STATE
{
NONE,
JOB_EMPTY,
JOB_STANBY,
JOB_WORKING,
JOB_WAITING,
JOB_DROP,
}
public delegate void JobTodo(params object[] param);
public delegate void JobEndAction(params object[] param);
public delegate bool JobToDoCondition(params object[] param);
public delegate bool JobAutoDropCondition(params object[] param);
public string key;
public float repeatDelay;
public int repeatCount;
public int excuteCount;
public IEnumerator jobCoroutine;
public JobTodo jobTodo;
public JobEndAction jobEndAction;
public JobToDoCondition jobToDoCheck;
public JobAutoDropCondition jobAutoDropCheck;
public JOB_STATE state;
public IEnumerator worker;
public object[] parameter;
}
}


2) JobRepeatManager.cs


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using JobTodo = AT.JobManager.JobRepeatBase.JobTodo;
using JobToDoCondition = AT.JobManager.JobRepeatBase.JobToDoCondition;
using JobAutoDropCondition = AT.JobManager.JobRepeatBase.JobAutoDropCondition;
using JobEndAction = AT.JobManager.JobRepeatBase.JobEndAction;
using JOB_STATE = AT.JobManager.JobRepeatBase.JOB_STATE;
#if UNITY_EDITOR
using UnityEditor;
namespace AT.JobManager
{
[CustomEditor(typeof(JobRepeatManager))]
public class JobRepeatManagerEditor : Editor
{
private JobRepeatManager manager;
public override void OnInspectorGUI()
{
manager = (JobRepeatManager)target;
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Add Empty Job"))
{
int EmptyJobCount = manager.JobList.FindAll(job => job.key.Contains("EmptyJob")).Count;
manager.AddDelegateJob(string.Format("EmptyJob_{0}", EmptyJobCount), null);
}
if (GUILayout.Button("Drop All Job"))
{
if(manager.JobList.Count > 0)
{
foreach(var jobItem in manager.JobList)
{
DestroyImmediate(jobItem.gameObject);
}
manager.JobList.Clear();
}
}
EditorApplication.update();
EditorGUILayout.EndHorizontal();
DrawDefaultInspector();
}
}
}
#endif
namespace AT.JobManager
{
[ExecuteInEditMode]
public class JobRepeatManager : SingletonBase<JobRepeatManager>
{
// Job Information Container - Job Data Struct With Delegate Job
[SerializeField] protected List<JobRepeatBase> _jobList = new List<JobRepeatBase>();
public List<JobRepeatBase> JobList { get { return _jobList; } }
// Coroutine Job Information Container - Job Data Struct With Coroutine Job
[SerializeField] protected Dictionary<string, Coroutine> _coroutineJobList = new Dictionary<string, Coroutine>();
// Min DelayTime
protected float m_MinDelayTime = 0.1f;
public float MinDelayTime { get { return m_MinDelayTime; } set { m_MinDelayTime = value; } }
/// <summary>
/// Job RepeatManager > Adding Job
/// <para>key = JobKeyName,
/// todo = ExecuteFunctionPointer,
/// delay = Update Sequence Delay(Seconds),
/// repeatCount = Total Execute Count,
/// parameter = params object[] : Your Parameter
/// todoCondition = Execute Condition(true = Available Execute, false = Block Execute),
/// autoDropCondition = Job Drop Condition(Flag : true = Drop, false = MoveNext)</para>
/// </summary>
public bool AddDelegateJob(string key, JobTodo toDo, float delay = 1.0f, int repeatCount = 0, object[] parameter = null,
JobEndAction endActionWhenDrop = null,
JobToDoCondition toDoCondition = null, JobAutoDropCondition autoDropCondition = null, bool isImmediately = true)
{
// Already Registered Job Check
if (JobList.Find(job => job.key.Equals(key)) != null)
return false;
GameObject JobObject = new GameObject(key);
JobObject.transform.parent = this.transform;
JobRepeatBase newJob = JobObject.AddComponent<JobRepeatBase>();
newJob.key = key;
newJob.jobCoroutine = null;
newJob.jobTodo = toDo;
newJob.repeatDelay = delay;
newJob.repeatCount = repeatCount;
newJob.excuteCount = 0;
newJob.jobToDoCheck = toDoCondition;
newJob.jobAutoDropCheck = autoDropCondition;
newJob.jobEndAction = endActionWhenDrop;
newJob.state = JOB_STATE.JOB_STANDBY;
newJob.worker = CoJobHandle(key);
newJob.parameter = parameter;
if(toDo == null)
{
Debug.LogWarningFormat("Are You Sure Adding Empty Job? Todo Parameter is null (key:{0})", key);
newJob.state = JOB_STATE.JOB_EMPTY;
}
newJob.repeatDelay = newJob.repeatDelay < m_MinDelayTime ? m_MinDelayTime : newJob.repeatDelay;
JobList.Add(newJob);
if (isImmediately)
{
StartCoroutine(newJob.worker);
}
return true;
}
/// <summary>
/// Job RepeatManager > Coroutine Type Adding Job
/// <para>key = JobKeyName,
/// todo = Coroutine Type Todo,
/// delay = Update Sequence Delay(Seconds),
/// repeatCount = Total Execute Count,
/// parameter = params object[] : Your Parameter
/// todoCondition = Execute Condition(Flag : true = Available Execute, false = Block Execute),
/// autoDropCondition = Job Drop Condition(Flag : true = Drop, false = MoveNext),
/// </summary>
public bool AddCoroutineJob(string key, IEnumerator coroutineJobTodo, float delay = 1.0f, int repeatCount = 0, object[] param = null,
JobToDoCondition toDoCondition = null, JobAutoDropCondition autoDropCondition = null,
bool isImmediately = true)
{
// Already Registered Job Check
if (JobList.Find(job => job.key.Equals(key)) != null)
return false;
GameObject JobObject = new GameObject(key);
JobObject.transform.parent = this.transform;
JobRepeatBase newJob = JobObject.AddComponent<JobRepeatBase>();
newJob.key = key;
newJob.jobCoroutine = coroutineJobTodo;
newJob.jobTodo = null;
newJob.repeatDelay = delay;
newJob.repeatCount = repeatCount;
newJob.excuteCount = 0;
newJob.jobToDoCheck = toDoCondition;
newJob.jobAutoDropCheck = autoDropCondition;
newJob.state = JOB_STATE.JOB_STANDBY;
newJob.worker = CoJobHandle(key);
newJob.parameter = param;
if (coroutineJobTodo == null)
{
Debug.LogWarningFormat("Are You Sure Adding Empty Job? Todo Parameter is null (key:{0})", key);
newJob.state = JOB_STATE.JOB_EMPTY;
}
newJob.repeatDelay = newJob.repeatDelay < m_MinDelayTime ? m_MinDelayTime : newJob.repeatDelay;
JobList.Add(newJob);
if (isImmediately)
{
StartCoroutine(newJob.worker);
}
return true;
}
private void Start()
{
StartCoroutine(CoAutoDropWorkers());
}
public bool RemoveJob(string key)
{
JobRepeatBase findJob = JobList.Find(job => job.key.Equals(key));
if (findJob == null)
return false;
DestroyImmediate(findJob.gameObject);
return JobList.Remove(findJob);
}
public bool JobStart(string key)
{
JobRepeatBase findJob = JobList.Find(job => job.key.Equals(key));
if (findJob == null)
return false;
StopCoroutine(findJob.worker);
findJob.state = JOB_STATE.JOB_STANDBY;
StartCoroutine(findJob.worker);
return true;
}
public int JobDropAll()
{
int droppedJobCount = 0;
if(JobList.Count > 0)
{
JobList.Clear();
droppedJobCount = transform.childCount;
transform.DestroyAllChildren();
}
return droppedJobCount;
}
public bool ChangeJobDelay(string key, float newDelay)
{
JobRepeatBase findJob = JobList.Find(job => job.key.Equals(key));
if (findJob == null)
return false;
findJob.repeatDelay = newDelay;
StopCoroutine(findJob.worker);
findJob.state = JOB_STATE.JOB_STANDBY;
StartCoroutine(findJob.worker);
return true;
}
public bool ChangeRepeatCount(string key, int repeatCount)
{
JobRepeatBase findJob = JobList.Find(job => job.key.Equals(key));
if (findJob == null)
return false;
findJob.repeatCount = repeatCount;
StopCoroutine(findJob.worker);
findJob.state = JOB_STATE.JOB_STANDBY;
StartCoroutine(findJob.worker);
return true;
}
public bool AddFunctionChain(string key, JobTodo Todo = null, JobToDoCondition toDoCheck = null, JobAutoDropCondition autoDropCondition = null, bool isExecuteImmediately = true)
{
JobRepeatBase Job = JobList.Find(job => job.key.Equals(key));
if (Job == null)
return false;
StopCoroutine(Job.worker);
Job.jobTodo += Todo;
Job.jobToDoCheck += toDoCheck;
Job.jobAutoDropCheck += autoDropCondition;
if (Job.jobTodo == null)
{
Job.state = JOB_STATE.JOB_EMPTY;
return true;
}
if (isExecuteImmediately)
{
Job.state = JOB_STATE.JOB_STANDBY;
StartCoroutine(Job.worker);
}
return true;
}
public bool RemoveFunctionChain(string key, JobTodo Todo = null, JobToDoCondition toDoCheck = null,
JobAutoDropCondition autoDropCondition = null, bool isExecuteImmediately = true)
{
JobRepeatBase Job = JobList.Find(job => job.key.Equals(key));
if (Job == null)
return false;
StopCoroutine(Job.worker);
Job.jobTodo -= Todo;
Job.jobToDoCheck -= toDoCheck;
Job.jobAutoDropCheck -= autoDropCondition;
if (Job.jobTodo == null)
{
Job.state = JOB_STATE.JOB_EMPTY;
return true;
}
if (isExecuteImmediately)
{
Job.state = JOB_STATE.JOB_STANDBY;
StartCoroutine(Job.worker);
}
return true;
}
public JobRepeatBase GetJobBase(string key)
{
return JobList.Find(x => x.key == key);
}
private WaitForSeconds _dropManagingDelay = new WaitForSeconds(3.0f);
private IEnumerator CoAutoDropWorkers()
{
while (gameObject.activeSelf)
{
var dropItems = JobList.FindAll(Job => Job.state == JOB_STATE.JOB_DROP);
foreach(var dropItem in dropItems)
{
dropItem.jobEndAction?.Invoke(dropItem.parameter);
JobList.Remove(dropItem);
DestroyImmediate(dropItem.gameObject);
}
yield return _dropManagingDelay;
}
}
private IEnumerator CoJobHandle(string key)
{
yield return null;
JobRepeatBase findJob = JobList.Find(x => x.key == key);
if (findJob == null)
yield break;
switch (findJob.state)
{
case JOB_STATE.JOB_EMPTY:
yield break;
case JOB_STATE.JOB_STANDBY:
if (findJob.jobToDoCheck != null)
{
if (findJob.jobToDoCheck(findJob.parameter))
{
findJob.state = JOB_STATE.JOB_WORKING;
findJob.jobTodo?.Invoke(findJob.parameter);
if (findJob.jobCoroutine != null)
yield return StartCoroutine(findJob.jobCoroutine);
findJob.excuteCount++;
if (findJob.excuteCount >= findJob.repeatCount && findJob.repeatCount != 0)
findJob.state = JOB_STATE.JOB_DROP;
else
findJob.state = JOB_STATE.JOB_WAITING;
}
}
else
{
findJob.state = JOB_STATE.JOB_WORKING;
findJob.jobTodo?.Invoke(findJob.parameter);
if (findJob.jobCoroutine != null)
yield return StartCoroutine(findJob.jobCoroutine);
findJob.excuteCount++;
if (findJob.excuteCount >= findJob.repeatCount && findJob.repeatCount != 0)
findJob.state = JOB_STATE.JOB_DROP;
else
findJob.state = JOB_STATE.JOB_WAITING;
}
if (findJob.jobAutoDropCheck != null)
{
if (findJob.jobAutoDropCheck(findJob.parameter))
{
findJob.state = JOB_STATE.JOB_DROP;
break;
}
}
break;
case JOB_STATE.JOB_WAITING:
WaitForSeconds WaitForDelay = new WaitForSeconds(findJob.repeatDelay);
yield return WaitForDelay;
findJob.state = JOB_STATE.JOB_STANDBY;
break;
case JOB_STATE.JOB_DROP:
yield break;
}
yield return StartCoroutine(CoJobHandle(findJob.key));
}
}
}



'Unity Engine > Unity3D Engine' 카테고리의 다른 글

#04.C# Job System_01  (0) 2018.12.22
#03.JobSequenceManager  (0) 2018.12.21
#02.JobRepeatManager - 03  (0) 2018.12.20
#02.JobRepeatManager - 02  (0) 2018.12.20
#01.A에서 B로 일정 시간동안 움직이기  (0) 2018.12.18