[MMO 컨텐츠 구현] 3. Job: Job Serializer & Job Timer

scarleter99·2023년 11월 21일

Job

  • JobSerializer에 등록할 함수를 캡슐화한 클래스이다.
  • 실행할 함수와 매개변수를 관리한다.

📄 Job.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace Server.Game
{
	public interface IJob
	{
		void Execute();
	}

	public class Job : IJob
	{
		Action _action;

		public Job(Action action)
		{
			_action = action;
		}

		public void Execute()
		{
			_action.Invoke();
		}
	}

	public class Job<T1> : IJob
	{
		Action<T1> _action;
		T1 _t1;

		public Job(Action<T1> action, T1 t1)
		{
			_action = action;
			_t1 = t1;
		}

		public void Execute()
		{
			_action.Invoke(_t1);
		}
	}

	public class Job<T1, T2> : IJob
	{
		Action<T1, T2> _action;
		T1 _t1;
		T2 _t2;

		public Job(Action<T1, T2> action, T1 t1, T2 t2)
		{
			_action = action;
			_t1 = t1;
			_t2 = t2;
		}

		public void Execute()
		{
			_action.Invoke(_t1, _t2);
		}
	}

	public class Job<T1, T2, T3> : IJob
	{
		Action<T1, T2, T3> _action;
		T1 _t1;
		T2 _t2;
		T3 _t3;

		public Job(Action<T1, T2, T3> action, T1 t1, T2 t2, T3 t3)
		{
			_action = action;
			_t1 = t1;
			_t2 = t2;
			_t3 = t3;
		}

		public void Execute()
		{
			_action.Invoke(_t1, _t2, _t3);
		}
	}
}

JobTimer

JobTimerElem

  • JobTimer에 등록할 Job을 캡슐화한 구조체이다.
  • Job과 실행 시간을 관리한다.

📄 JobTimer.cs

using System;
using System.Collections.Generic;
using System.Text;
using ServerCore;

namespace Server.Game
{
	struct JobTimerElem : IComparable<JobTimerElem>
	{
		public int execTick; // 실행 시간
		public IJob job;

		public int CompareTo(JobTimerElem other)
		{
			return other.execTick - execTick;
		}
	}
    
// ...

JobTimer

  • 예약된 Job을 JobTimerElem로 변환하여 관리한다.
  • 예약 실행 시간이 된 Job을 전부 실행한다.

📄 JobTimer.cs

// ...

	public class JobTimer
	{
		PriorityQueue<JobTimerElem> _pq = new PriorityQueue<JobTimerElem>();
		object _lock = new object();

		public void Push(IJob job, int tickAfter = 0)
		{
			JobTimerElem jobElement;
			jobElement.execTick = System.Environment.TickCount + tickAfter;
			jobElement.job = job;

			lock (_lock)
			{
				_pq.Push(jobElement);
			}
		}

        // 예약 실행 시간이 된 Job 전부 실행
        public void Flush()
		{
			while (true)
			{
				int now = System.Environment.TickCount;

				JobTimerElem jobElement;

				lock (_lock)
				{
					if (_pq.Count == 0)
						break;

					jobElement = _pq.Peek();
					if (jobElement.execTick > now)
						break;

					_pq.Pop();
				}

				jobElement.job.Execute();
			}
		}
	}
}

JobSerializer

  • 등록된 Job을 Queue로, 예약된 Job을 JobTimer로 모두 관리한다.
  • 일정 시간마다 등록된 Job과 예약된 Job을 전부 순차적으로 실행한다.

📄 JobSerializer.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace Server.Game
{
    public class JobSerializer
    {
        JobTimer _timer = new JobTimer(); // 예약된 Job들
        Queue<IJob> _jobQueue = new Queue<IJob>(); // 등록된 Job들
        object _lock = new object();
        bool _flush = false; // 현재 쓰레드에서 Flush 진행 중 여부

        public void PushAfter(int tickAfter, Action action) { PushAfter(tickAfter, new Job(action)); }
        public void PushAfter<T1>(int tickAfter, Action<T1> action, T1 t1) { PushAfter(tickAfter, new Job<T1>(action, t1)); }
        public void PushAfter<T1, T2>(int tickAfter, Action<T1, T2> action, T1 t1, T2 t2) { PushAfter(tickAfter, new Job<T1, T2>(action, t1, t2)); }
        public void PushAfter<T1, T2, T3>(int tickAfter, Action<T1, T2, T3> action, T1 t1, T2 t2, T3 t3) { PushAfter(tickAfter, new Job<T1, T2, T3>(action, t1, t2, t3)); }

        // Job 예약
        public void PushAfter(int tickAfter, IJob job)
        {
            _timer.Push(job, tickAfter);
        }

        public void Push(Action action) { Push(new Job(action)); }
        public void Push<T1>(Action<T1> action, T1 t1) { Push(new Job<T1>(action, t1)); }
        public void Push<T1, T2>(Action<T1, T2> action, T1 t1, T2 t2) { Push(new Job<T1, T2>(action, t1, t2)); }
        public void Push<T1, T2, T3>(Action<T1, T2, T3> action, T1 t1, T2 t2, T3 t3) { Push(new Job<T1, T2, T3>(action, t1, t2, t3)); }

        // Job 등록
        public void Push(IJob job)
        {
            lock (_lock)
            {
                _jobQueue.Enqueue(job);
            }
        }

        // 등록된 Job과 예약 실행 시간이 지난 Job 전부 실행
        public void Flush()
        {
            // 예약 실행 시간이 지난 Job 전부 실행
            _timer.Flush();

            while (true)
            {
                IJob job = Pop();
                if (job == null)
                    return;

                job.Execute();
            }
        }


        IJob Pop()
        {
            lock (_lock)
            {
                if (_jobQueue.Count == 0)
                {
                    _flush = false;
                    return null;
                }
                return _jobQueue.Dequeue();
            }
        }
    }
}

빌드 및 실행

0개의 댓글