Command 패턴과 Job
서버 게임룸에서 우리가 lock을 덕지덕지 사용해서 어마어마한 딜레이를 발생시킬 수도 있다.
그 문제를 우리가 해결해야하는데 그 방안으로 Command 패턴을 적용해보자.
즉 한마디로 게임 룸에게 일감을 던져주고 다른 쓰레드는 다시 원래 본업을 하러 가는 것이 핵심이다.
그렇기에 일감을 던져줘야하는데 그 일감들의 데이터, 실행할 함수를 저정할 클래스인 Job을 만들어준다.
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game.Job
{
public interface IJob
{
void Execute();
}
public class Job : IJob
{
Action _action;
public Job(Action action)
{
_action = action;
}
public void Execute()
{
}
}
public class Job<T1> : IJob
{
Action<T1> _action;
T1 _t1;
public Job(Action<T1> action, T1 t1)
{
_action = action;
_t1 = t1;
}
public void Execute()
{
}
}
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()
{
}
}
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()
{
}
}
}
인자를 3개까지 받을 수 있게 했다. 4개 이상 5개 이상을 만드려면 그냥 추가만 하면 된다. 이게 노가다처럼 보이겠지만 실제 C# Action도 이런 형식으로 구현되어있다.
이제 저 Job을 담아둘 공간인 JobSerializer 클래스를 만들어 준다.
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game.Job
{
public class JobSerializer
{
Queue<IJob> _jobQueue = new Queue<IJob>();
object _lock = new object();
bool _flush = false;
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)); }
public void Push(IJob job)
{
bool flush = false;
lock (_lock)
{
_jobQueue.Enqueue(job);
if (_flush == false)
flush = _flush = true;
}
if (flush)
Flush();
}
void 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();
}
}
}
}
이렇게 함으로써 GameRoom에서 사용하던 lock을 다 지울 수 있게 됐다.
JobTimer
클라쪽에서 오는 패킷들의 요청들이 오면 Job이 쌓이게 되고 그것을 순차적으로 처리하고 있다. 그런데 만약 이 방안에 패킷을 보내는 플레이어가 단 한명도 없다면 GameRoom에서 사용하고 있는 Update를 누군가는 계속 실행햐 주어야만 한다. 우리가 그전에는 서버를 실행할 때 무한루프를 통해서 이 부분을 처리했다.
또한, 그리고 특정 작업을 예약하고 싶을 때가 있다. 우리가 이전에 만들었던 몬스터 ai를 보면 특정 틱(1초) 마다 실행하게끔 했는데 그 방식이 되게 무식하다고 할 수 있다.
클라에서는 이를 해결할 때 코루틴을 통해서 해결할 수 있었다.
서버에서는 Job Timer를 이용해서 효율적으로 처리해보자.
using System;
using System.Collections.Generic;
using System.Text;
using ServerCore;
namespace Server.Game.Job
{
struct JobTimerElem : IComparable<JobTimerElem>
{
public int execTick; // 실행 시간
public IJob job;
public int CompareTo(JobTimerElem other)
{
return other.execTick - execTick;
}
}
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 = Environment.TickCount + tickAfter;
jobElement.job = job;
lock (_lock)
{
_pq.Push(jobElement);
}
}
public void Flush()
{
while (true)
{
int now = Environment.TickCount;
JobTimerElem jobElement;
lock (_lock)
{
if (_pq.Count == 0)
break;
jobElement = _pq.Peek();
if (jobElement.execTick > now)
break;
_pq.Pop();
}
jobElement.job.Execute();
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Server.Game.Job
{
public class JobSerializer
{
JobTimer _timer = new JobTimer();
Queue<IJob> _jobQueue = new Queue<IJob>();
object _lock = new object();
bool _flush = false;
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)); }
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)); }
public void Push(IJob job)
{
lock (_lock)
{
_jobQueue.Enqueue(job);
}
}
public void Flush()
{
_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();
}
}
}
}
'Unity > 온라인 RPG' 카테고리의 다른 글
[Unity 2D] Db 연동 - Item과 Inventory (0) | 2024.03.27 |
---|---|
[Unity 2D] DB 연동 - Player, HP db 연동 (0) | 2024.03.26 |
[Unity 2D] 서버 연동 - Hp bar와 DieEffect 그리고 몬스터 Ai (0) | 2024.03.13 |
[Unity 2D] 서버 연동 - Data & Config 과 피격 (0) | 2024.03.12 |
[Unity 2D] 서버 연동 - 스킬 동기화와 히트 판정 (0) | 2024.03.11 |