전체 코드

📌 기존 서버 구조와 문제점 분석

⚠ 기존 Tick을 이용한 방식의 문제점

현재 서버는 일정 시간 간격마다 Room.Flush()를 실행하는 구조

while (true)
{
    Room.Push(() => Room.Flush());
    Thread.Sleep(250); 
}

기존 방식의 특징

  • 특정 주기(0.25초)마다 Room.Flush() 실행
  • Thread.Sleep()을 사용하여 일정 간격 유지
  • 모든 작업을 같은 간격으로 실행

⚠ 문제점 분석

일정 주기 실행의 비효율성

  • AI, 스킬 사용, 몬스터 이동 등 각 작업마다 실행 주기가 다름
  • 모든 작업이 같은 주기로 실행되는 건 비효율적

비효율적인 시간 체크

  • while 문 안에서 모든 작업을 체크해야 함
  • 작업이 많아질수록 if 문 체크가 증가하여 성능 저하

우선순위 없는 실행 방식

  • 시간이 오래 남은 작업과 바로 실행할 작업을 동일한 방식으로 관리

📌 해결책: JobTimer (예약 시스템) 적용

🔹 JobTimer 개념

  1. 작업을 예약(Push)하고 특정 시간에 실행(Flush)
  2. 작업마다 실행 시간이 다름개별적으로 실행 시간 관리 가능
  3. 우선순위 큐(Priority Queue) 사용
    • 가장 실행 시간이 임박한 작업을 먼저 실행

기대 효과

  • 필요한 시간에만 실행 → CPU 사용량 감소
  • 여러 개의 Room, AI, 몬스터 동작 등 다양한 주기로 실행 가능
  • 우선순위 큐 활용 → O(logN)으로 빠르게 실행

📌 1️⃣ JobTimerElem 구조

JobTimerElem은 실행할 작업과 실행 시간을 포함하는 구조체

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

namespace Server
{
    struct JobTimerElem : IComparable<JobTimerElem>
    {
        public int execTick; // 실행 시간
        public Action action; // 실행할 작업

        public int CompareTo(JobTimerElem other)
        {
            // 실행 시간이 빠른 순서로 정렬
            return other.execTick - execTick;
        }
    }
}

핵심 개선 사항

  • execTick: 실행될 시간
  • action: 실행할 작업
  • CompareTo(): 작업을 실행 시간이 빠른 순서대로 정렬

📌 2️⃣ JobTimer 구현

✅ JobTimer.cs - Job 관리 및 실행

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

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

        public static JobTimer Instance { get; } = new JobTimer();

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

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

        public void Flush()
        {
            while (true)
            {
                int now = System.Environment.TickCount;
                JobTimerElem job;

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

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

                    _pq.Pop();
                }

                job.action.Invoke();
            }
        }
    }
}

핵심 개선 사항

  • Push(): 실행할 작업을 execTick 시간에 맞게 예약
  • Flush(): 실행 시간이 된 작업을 실행
  • while 문을 통해 실행 시간이 가장 임박한 작업을 먼저 실행

📌 3️⃣ 서버의 메인 루프 개선

✅ ServerProgram.cs - JobTimer 활용

using System.Net;
using System.Threading;
using ServerCore;

namespace Server
{
    class ServerProgram
    {
        static Listener _listener = new Listener();
        public static GameRoom Room = new GameRoom();

        static void FlushRoom()
        {
            Room.Push(() => Room.Flush());
            JobTimer.Instance.Push(FlushRoom, 250);
        }

        static void Main(string[] args)
        {
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 1111);

            _listener.Init(endPoint, () => { return SessionManager.Instance.Generate(); });
            System.Console.WriteLine("Listening...");

            JobTimer.Instance.Push(FlushRoom);

            while (true)
            {
                JobTimer.Instance.Flush();
            }
        }
    }
}

핵심 개선 사항

  • FlushRoom() 함수로 Room.Flush() 실행을 JobTimer에 예약
  • JobTimer.Instance.Push(FlushRoom, 250)0.25초마다 실행 예약
  • 메인 루프에서 JobTimer.Flush() 실행하여 예약된 작업 실행

📌 4️⃣ 우선순위 큐(Priority Queue) 구현

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

namespace ServerCore
{
    public class PriorityQueue<T> where T : IComparable<T>
    {
        List<T> _heap = new List<T>();

        public int Count { get { return _heap.Count; } }

        public void Push(T data)
        {
            _heap.Add(data);
            int now = _heap.Count - 1;

            while (now > 0)
            {
                int next = (now - 1) / 2;
                if (_heap[now].CompareTo(_heap[next]) < 0)
                    break;

                T temp = _heap[now];
                _heap[now] = _heap[next];
                _heap[next] = temp;

                now = next;
            }
        }

        public T Pop()
        {
            T ret = _heap[0];
            int lastIndex = _heap.Count - 1;
            _heap[0] = _heap[lastIndex];
            _heap.RemoveAt(lastIndex);
            lastIndex--;

            int now = 0;
            while (true)
            {
                int left = 2 * now + 1;
                int right = 2 * now + 2;
                int next = now;

                if (left <= lastIndex && _heap[next].CompareTo(_heap[left]) < 0)
                    next = left;
                if (right <= lastIndex && _heap[next].CompareTo(_heap[right]) < 0)
                    next = right;

                if (next == now)
                    break;

                T temp = _heap[now];
                _heap[now] = _heap[next];
                _heap[next] = temp;
                now = next;
            }

            return ret;
        }

        public T Peek()
        {
            if (_heap.Count == 0)
                return default(T);
            return _heap[0];
        }
    }
}

핵심 개선 사항

  • Push(): 새로운 작업을 추가하며 Heap 정렬
  • Pop(): 실행할 작업을 꺼내면서 Heap 유지
  • Peek(): 가장 빠른 실행 시간을 가진 작업 조회

profile
李家네_공부방

0개의 댓글