24-04

Sandy·2024년 5월 2일

[Today I Learned]

목록 보기
16/18

240401_FTimerDelegate

// 14003번 가장 긴 증가하는 부분 수열 5
/*
증가수열 만들기
각 dp에 저장한 idx 저장하기
뒤에서부터 출력하기
*/

FTimerHandle

FTimerDelegate::CreateUObject()

UFUNCTION(BlueprintImplementableEvent)

UGameplayStatics::GetAllActorsOfClass()

UGameplayStatics::SpawnEmitterAtLocation()

240402_USoundBase

// 27172번 수 나누기 게임
/*
그냥 다 돌리면서 나눠지는지 체크하기

- N^2으로 되어서 접근시간 줄이기 필요
*/

UParticleSystem

USoundBase

UGameplayStatics::PlaySoundAtLocation()

TSubclassOf<>

240403_CCW

// 2166번 다각형의 면적
/*
센터 하나에서 점 두개 골라서 벡터 만들고 외적하기
넓이 계속 더하면서 전체 넓이 구하기
절댓값, /2하기
*/

240404_Input

AddMovementInput()

AddControllerPitchInput()

EInputEvent::IE_Pressed

240405_FPointDamageEvent

SpawnActor<>()

TSubclassOf<>()

HideBoneByName()

AttachToComponent()

SetOwner()

GetPlayerViewPoint()

LineTraceSingleByChannel()

FPointDamageEvent

TakeDamage()

240406_BlueprintPure

UFUNCTION(BlueprintPure)

UGameplayStatics::GetPlayerPawn

240407_UBehaviorTreeComponent

ExecuteTask()

UBehaviorTreeComponent

240408_TActorRange

TickNode()

FTimerHandle

TActorRange<>()

240415_GetCurrentAnimatorStateInfo

anim.GetCurrentAnimatorStateInfo(0).IsName("card_flip")

레이어에서 현재 진행중인 애니메이션 갖고오기

Invoke("closeCardInvoke", 0.5f);

몇 초 뒤 함수 실행

240416_ReaderWriter

Task

내부적으로 ThreadPool 사용

Thread.MemoryBarrier();

코드 재배치 막기

가시성

Monitor.Enter(_obj); / Monitor.Exit(_obj);

lock (_obj){}

SpinLock


namespace ServerCore
{
    class SpinLock
    {
        volatile int _locked = 0;
        public void Acquire()
        {
            while (true)
            {
		            /*int original = Interlocked.Exchange(ref _locked, 1);
                if (original == 0)
                {
                    break;
                }*/

                int expected = 0;
                int desired = 1;
                if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
                    break;
                    
                /*Thread.Sleep(1);
                Thread.Sleep(0);*/
                Thread.Yield();
            }
        }

        public void Release() 
        { 
            _locked = 0;
        }
    }
    class Program
    {
        static int count = 0;
        static SpinLock _lock = new SpinLock();

        static void Thread_1()
        {
            for (int i = 0; i < 100000; i++)
            {
                _lock.Acquire();
                count++;
                _lock.Release(); 
            }
        }

        static void Thread_2()
        {
            for (int i = 0; i < 100000; i++)
            {
                _lock.Acquire();
                count--;
                _lock.Release();
            }
        }

        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine(count);
        }
    }
}

커널 이벤트

using System;
using System.Threading;

namespace ServerCore
{
    class Lock
    {
        AutoResetEvent _available = new AutoResetEvent(true);

        public void Acquire()
        {
            _available.WaitOne();
        }

        public void Release() 
        {
            _available.Set();
        }
    }
    class Program
    {
        static int count = 0;
        static Lock _lock = new Lock();

        static void Thread_1()
        {
            for (int i = 0; i < 100; i++)
            {
                _lock.Acquire();
                count++;
                _lock.Release(); 
            }
        }

        static void Thread_2()
        {
            for (int i = 0; i < 100; i++)
            {
                _lock.Acquire();
                count--;
                _lock.Release();
            }
        }

        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine(count);
        }
    }
}

Mutex 느려서 사용 X

using System;
using System.Threading;

namespace ServerCore
{
    class Program
    {
        static int count = 0;
        static Mutex _lock = new Mutex();

        static void Thread_1()
        {
            for (int i = 0; i < 10000; i++)
            {
                _lock.WaitOne();
                count++;
                _lock.ReleaseMutex();
            }
        }

        static void Thread_2()
        {
            for (int i = 0; i < 10000; i++)
            {
                _lock.WaitOne();
                count--;
                _lock.ReleaseMutex();
            }
        }

        static void Main(string[] args)
        {
            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine(count);
        }
    }
}

ReaderWriter

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ServerCore
{
    // 재귀적 lock [X]
    // 스핀락 (5000번 -> Yield)
    class Lock
    {
        const int EMPTY_FLAG = 0x00000000;
        const int WRITE_MASK = 0x7fff0000;
        const int READ_MASK = 0x0000ffff;
        const int MAX_SPIN_COUNT = 5000;

        // [Unused] [WriteThreadID 15] [ReadCount 16]
        int _flag = EMPTY_FLAG;

        public void WriteLock()
        {
            int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
            while(true)
            {
                for(int i = 0; i < MAX_SPIN_COUNT; i++)
                {
                    if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
                    {
                        return;
                    }
                }

                Thread.Yield();
            }
        }

        public void WriteUnlock()
        {
            Interlocked.Exchange(ref _flag, EMPTY_FLAG);
        }

        public void ReadLock()
        {
            while (true)
            {
                for (int i = 0; i < MAX_SPIN_COUNT; i++)
                {
                    int expected = (_flag & READ_MASK);
                    if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
                    {
                        return;
                    }
                }

                Thread.Yield();
            }
        }

        public void ReadUnlock() 
        {
            Interlocked.Decrement(ref _flag);
        }
    }
}

ReaderWriter - 동일 스레드 연속해서 lock 잡기

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ServerCore
{
    // 재귀적 [O] WriteLock -> WriteLock [O], WriteLock -> ReadLock [O] 
    // 스핀락 (5000번 -> Yield)
    class Lock
    {
        const int EMPTY_FLAG = 0x00000000;
        const int WRITE_MASK = 0x7fff0000;
        const int READ_MASK = 0x0000ffff;
        const int MAX_SPIN_COUNT = 5000;

        // [Unused] [WriteThreadID 15] [ReadCount 16]
        int _flag = EMPTY_FLAG;
        int _writeCount = 0;

        public void WriteLock()
        {
            // 동일 스레드에서 WriteLock을 이미 획득한 경우
            int lockThreadId = (_flag & WRITE_MASK) >> 16;
            if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
            {
                _writeCount++;
                return;
            }

            // WriteLock 얻기 
            int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
            while(true)
            {
                for(int i = 0; i < MAX_SPIN_COUNT; i++)
                {
                    if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
                    {
                        _writeCount = 1;
                        return;
                    }
                }

                Thread.Yield();
            }
        }

        public void WriteUnlock()
        {
            int lockCount = --_writeCount;
            if (lockCount == 0)
            {
                Interlocked.Exchange(ref _flag, EMPTY_FLAG);
            }
        }

        public void ReadLock()
        {
            // 동일 스레드에서 WriteLock을 이미 획득한 경우
            int lockThreadId = (_flag & WRITE_MASK) >> 16;
            if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
            {
                Interlocked.Increment(ref _flag);
                return;
            }

            while (true)
            {
                for (int i = 0; i < MAX_SPIN_COUNT; i++)
                {
                    int expected = (_flag & READ_MASK);
                    if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
                    {
                        return;
                    }
                }

                Thread.Yield();
            }
        }

        public void ReadUnlock() 
        {
            Interlocked.Decrement(ref _flag);
        }
    }
}

240417_CardFlip

1. 작업 내용

1 클릭할 때(카드 뒤집을 때), 시작할 때, 진행 중일 때 성공, 실패 소리 넣어보기

2 한 번씩 뒤집은 카드는 색을 다르게 표시하기 (옅은 회색 등)

3 카드 뒤집기에서 실제로 카드가 뒤집어지는 모습 연출하기

4 firstCard 고르고 5초 간 카운트 다운 - 안 고르면 다시 닫기

2. 구현 방식

1 클릭할 때(카드 뒤집을 때), 시작할 때, 진행 중일 때 성공, 실패 소리 넣어보기

클릭할 때(카드 뒤집을 때)

card.cs

public void OpenCard()
{
    if(GameManager.instance.isStart == true)
    {
        audioSource.PlayOneShot(flipSound);
        anim.SetBool("isOpen", true);

    }
}

시작할 때

GameManager.cs

void Start()
{
    audioSource.PlayOneShot(gameStartSound);
}

진행 중일 때 성공, 실패 소리

GameManager.cs

public void Matched()
{
    if(firstCard.idx == secondCard.idx)
    {
        audioSource.PlayOneShot(matchedSound);
        firstCard.DestroyCard();
        secondCard.DestroyCard();
        cardCount -= 2;
        SettingNameTxt();
        NameTxt.color = Color.white;
    }

    else
    {
        NameTxt.text = "실패!";
        NameTxt.color = Color.red;
        time -= 1f;
        IncreaseTime.SetActive(true);
        audioSource.PlayOneShot(unMatchedSound);
        Invoke("TxtControl", 0.5f);
        firstCard.CloseCard();
        secondCard.CloseCard();
    }

}

2 한 번씩 뒤집은 카드는 색을 다르게 표시하기 (옅은 회색 등)

Card.cs

public void OpenCard()
{
    if(GameManager.instance.isStart == true)
    {
        if (!isFlipedOnce)
        {
            isFlipedOnce = true;
            SpriteRenderer spriteRenderer = back.GetComponent<SpriteRenderer>();

            spriteRenderer.color = new Color(0.7f, 0.7f, 0.7f, 1);
        }
    }
}

3 카드 뒤집기에서 실제로 카드가 뒤집어지는 모습 연출하기

CardFliping 애니메이션 추가

CardFliping 끝나면 CardFliped로 변경

4 firstCard 고르고 5초 간 카운트 다운 - 안 고르면 다시 닫기

GameManager.cs

void Update()
{
    if (firstCard)
    {
        timeAfterFirstCardFlip -= Time.deltaTime;
        if (timeAfterFirstCardFlip <= 0)
        {
            firstCard.GetComponent<Card>().CloseCard();
            SetTimeAfterFirstCardFlip(0.0f);
            firstCard = null;
        }
    }
}

3. 기능 구현시 어려웠던 점

카드 뒤집는 애니메이션 - 스크립트로 뒤집게 했는데 원하던 내용대로 실행되지 않았다. 유니티 라이프사이클에 대한 이해가 부족했다.

첫 번째 카드 고른 후 5초 뒤 닫기 - 코루틴으로 사용하면 Update에서 5초 돌면서 체크를 안해도 될 것 같다. 코루틴 사용법을 잘 몰라 일단 타이머로 구현했다.

4. 왜 해당 코드를 사용했는지

카드 뒤집는 애니메이션 - Update에서 find로 현재 애니메이션 상태를 체크하는 코드를 넣었었다. Update는 매 프레임 돌아간다. find에서는 string으로 찾아서 비싸다. 이 부분을 없애기 위해서 작동하는 부분을 애니메이션에 넣었다. 더 깔끔하게 하려면 cardState별로 나누고 해당 애니메이션을 재생하는 방법을 생각했다. CardIdle, CardFlipping, CardFliped로 새로운 상태가 생기면 더 추가하면 된다. 하지만 카드의 애니메이션이 많지 않고 조건도 별로 없어서 하지 않았다.

5. 문제 수정

Q csproj 파일이 불필요하게 다 생성된다

에디터 prference에서 Genereate csproj 옵션을 다 켜놨다. Embeded랑 local만 킨다.

Q 자식 오브젝트 애니메이션 적용하려는데 에디터에서 선택이 안된다

Filter by Selection 켜있었다. 클릭해서 끈다. 선택한 오브젝트에 적용되는 애니메이션만 표시해서 자식 오브젝트가 선택이 안됐다.

Q 카드 뒤집는 애니메이션으로 전환하는데 깜박이는 현상이 발생한다

Lifecycle 관련된 문제이다. Update Animation Render 순서로 진행된다. Animator가 Update 앞에서 되도록 Animate Physics 로 설정하면 발생하지 않는다. 문제 해결은 코드가 아니라 애니메이션에서 카드를 뒤집도록 바꾸었다

Q 에디터에서 한글 주석이 깨져서 보인다

코드를 저장해놓는 파일 인코딩 문제이다. utf-8로 변경한다. editorconfig파일 만들어서 utf-8로 전체 파일 저장하는 방식으로 설정한다. 다시 파일 열고 저장하면 바뀐다.

6. 정리

깃허브, 유니티 툴 사용 관련 문제가 있었다. 여러 사람이서 씬 배치를 하면 메타파일이 동시에 수정된다. pr을 했는데 하나하나 확인하면서 수정하기 힘들었다. 작업이 적어 작업 분배가 어려워서 기능별로 구분을 했다. 그래서 작업 부분이 겹쳤고 팀장님이 압축파일을 받아서 합치기로 했다. 그 이후부터 조금씩 추가하거나 수정했다. 합쳐진 버젼에 다시 내가 했던 작업물을 넣었다. 내 브랜치에서 메인 내용을 합치고 pr을 했다.

editorconfig를 추가하고 코드를 전부 utf-8로 바꾼 후에 pr를 했다. conflict나서 깃허브 웹으로 그 부분을 수정했다. svn에서는 이 블럭은 누가 한 걸로 선택하는지가 있는데 깃에서는 웹이나 커맨드로만 작업할 수 있는 것 같았다. 프로젝트 규모가 적당히 나눌만큼 된다면 문제가 줄어들 것 같다. 기능별로 작업 부분이 나뉜다. UI도 스크립트로 만들면 된다.

7. 느낀점

프로젝트 해보면서 해결해보는 경험이 좋았다. 강의 따라해보면서 하는 것보다 어떻게 개선할 수 있을까 고민하는 과정이 필요했다.

팀에서 할 일을 나누고 깃으로 공동작업을 했다. 내가 해야하는 작업은 작았다. 하지만 하나로 합치면서 시행착오를 거쳤다. 브랜치를 나누고 합치니까 각자 작업이 잘 보였다.

240418_Socket

Client

자신의 이름 얻고 IP주소 갖고오기

서버랑 통신할 소켓 생성, 서버의 IP 주소와 포트를 지정해서 연결 설정

소켓을 통해 서버에 메시지 전송

서버에서 받은 응답 수신, 화면에 출력

연결 종료

Server

자신의 이름 얻고 대한 IP주소 갖고오기

클라 연결 받을 소켓 생성하기, 서버의 IP주소, 포트 바인딩

소켓으로 클라 연결 수신 대기하기

클라에서 메시지 받고 화면 출력

클라에게 메시지 보내기

연결 종료

포트번호

각 프로세스가 가진 고유한 통신 채널

네트워크 패킷을 각 프로세스로 라우팅하는데 사용한다

Client


namespace DummyClient
{
    class Program
    {
        static void Main(string[] args)
        {
            // DNS
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

            // Socket
            Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                // Connect
                socket.Connect(endPoint);
                Console.WriteLine($"Connected To {socket.RemoteEndPoint.ToString()}");

                // Send
                byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World!");
                int sendBytes = socket.Send(sendBuff);

                // Receive
                byte[] recvBuff = new byte[1024];
                int recvBytes = socket.Receive(recvBuff);
                string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                Console.WriteLine($"[From Server] {recvData}");

                // Close
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

        }
    }
}

Server


namespace ServerCore
{
    class Program
    {
        static void Main(string[] args)
        {
            // DNS
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

            // Socket
            Socket listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                // Bind
                listenSocket.Bind(endPoint);

                // Listen
                listenSocket.Listen(10);

                while (true)
                {
                    Console.WriteLine("Listening...");

                    // Accept
                    Socket clientSocket = listenSocket.Accept();

                    // Receive
                    byte[] recvBuff = new byte[1024];
                    int recvBytes = clientSocket.Receive(recvBuff);
                    string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                    Console.WriteLine($"[From Client] {recvData}");

                    // Send
                    byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
                    clientSocket.Send(sendBuff);

                    // Close
                    clientSocket.Shutdown(SocketShutdown.Both);
                    clientSocket.Close();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            
        }
    }
}
 

non-blocking Listener

class Listener
{
    // Socket
    Socket _listenSocket;
    Action<Socket> _onAcceptHandler;

    public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
    {
        // Socket
        _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        _onAcceptHandler += onAcceptHandler;

        // Bind
        _listenSocket.Bind(endPoint);

        // Listen
        _listenSocket.Listen(10);

        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
        RegisterAccept(args);
    }

    void RegisterAccept(SocketAsyncEventArgs args)
    {
        args.AcceptSocket = null;

        bool pending = _listenSocket.AcceptAsync(args);
        if (pending == false)
        {
            OnAcceptCompleted(null, args);
        }
    }

    void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
    {
        if(args.SocketError == SocketError.Success)
        {
            _onAcceptHandler.Invoke(args.AcceptSocket);
        }
        else
        {
            Console.WriteLine(args.SocketError.ToString());
        }

        RegisterAccept(args);
    }

}

class Program
{
    static Listener _listener = new Listener();
    static void OnAcceptHandler(Socket clientSocket)
    {
        try
        {
            // Receive
            byte[] recvBuff = new byte[1024];
            int recvBytes = clientSocket.Receive(recvBuff);
            string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
            Console.WriteLine($"[From Client] {recvData}");

            // Send
            byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
            clientSocket.Send(sendBuff);

            // Close
            clientSocket.Shutdown(SocketShutdown.Both);
            clientSocket.Close();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
    static void Main(string[] args)
    {
        // DNS
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddr = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
        
        _listener.Init(endPoint, OnAcceptHandler);
        
        while (true)
        {
            ;
        }
    }
}

Session non-blocking receive, close

class Session
{
    Socket _socket;
    int _disconnected = 0;

    public void Start(Socket socket)
    {
        _socket = socket;

        // Receive
        SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
        recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
        recvArgs.SetBuffer(new byte[1024], 0, 1024);

        RegisterRecv(recvArgs);

    }

    // Send
    public void Send(byte[] sendBuff)
    {
        _socket.Send(sendBuff);
    }

    // Close
    public void Disconnet()
    {
        if (Interlocked.Exchange(ref _disconnected, 1) == 1)
        {
            return;
        }

        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    #region Network
    void RegisterRecv(SocketAsyncEventArgs args)
    {
        bool pending = _socket.ReceiveAsync(args);
        if (pending == false)
        {
            OnRecvCompleted(null, args);
        }
    }

    void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
    {
       if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                Console.WriteLine($"[From Client] {recvData}");

                RegisterRecv(args);
            }
            catch (Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
        }
        else
        {
            Console.WriteLine($"OnRecvCompleted Failed {args.SocketError}");
        }
    }
    #endregion

}

class Program
{
    static Listener _listener = new Listener();
    static void OnAcceptHandler(Socket clientSocket)
    {
        try
        {
            
            Session session = new Session();
            session.Start(clientSocket);

            byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");

            session.Send(sendBuff);

            Thread.Sleep(1000);
            session.Disconnet();

        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
    static void Main(string[] args)
    {
        // DNS
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddr = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
        
        _listener.Init(endPoint, OnAcceptHandler);
        
        while (true)
        {
            ;
        }
    }
}

240419_Session

Listener

서버에서 연결 수신, 새로운 세션 만들기

Session

클라-서버 연결

데이터 송수신

연결 해제

Connector

클라에서 연결 수신, 새로운 세션 만들기

SendQueue

받는 데이터 순서대로 넣어서 하나씩 꺼내서 작업하기

Session - SendQueue에 보낼 내용 담아두기

class Session
{
    Socket _socket;
    int _disconnected = 0;

    object _lock = new object();
    Queue<byte[]> _sendQueue = new Queue<byte[]>();
    bool _pending = false;
    SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();

    public void Start(Socket socket)
    {
        _socket = socket;

        // Receive
        SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
        recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
        recvArgs.SetBuffer(new byte[1024], 0, 1024);

        _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);

        RegisterRecv(recvArgs);

    }

    // Close
    public void Disconnet()
    {
        if (Interlocked.Exchange(ref _disconnected, 1) == 1)
        {
            return;
        }

        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    // Send
    public void Send(byte[] sendBuff)
    {
        lock (_lock)
        {
            _sendQueue.Enqueue(sendBuff);
            if (_pending == false)
            {
                RegisterSend();
            }
        }
    }

    #region Network
    // Send
    void RegisterSend()
    {
        _pending = true;
        byte[] buff = _sendQueue.Dequeue();
        _sendArgs.SetBuffer(buff, 0, buff.Length);

        bool pending = _socket.SendAsync(_sendArgs);
        if (pending == false)
        {
            OnSendCompleted(null, _sendArgs);
        }
    }

    // Send
    void OnSendCompleted(object sender, SocketAsyncEventArgs args)
    {
        lock (_lock)
        {
            if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
            {
                try
                {
                    if (_sendQueue.Count > 0)
                    {
                        RegisterSend();
                    }
                    else
                    {
                        _pending = false;
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine($"OnSendCompleted Failed {e}");
                }
            }
            else
            {
                Disconnet();
            }
        }
    }

    // Receive
    void RegisterRecv(SocketAsyncEventArgs args)
    {
        bool pending = _socket.ReceiveAsync(args);
        if (pending == false)
        {
            OnRecvCompleted(null, args);
        }
    }

    // Receive
    void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
    {
        if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                Console.WriteLine($"[From Client] {recvData}");

                RegisterRecv(args);
            }
            catch (Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
        }
        else
        {
            Disconnet();
        }
    }
    #endregion

}

Session - pendingList에다가 SendQueue에 있는 데이터 모아서 한번에 보내기

class Session
{
    Socket _socket;
    int _disconnected = 0;

    object _lock = new object();
    Queue<byte[]> _sendQueue = new Queue<byte[]>();
    List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
    SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
    SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();

    public void Start(Socket socket)
    {
        _socket = socket;

        // Receive
        _recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
        _recvArgs.SetBuffer(new byte[1024], 0, 1024);

        // Send
        _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);

        RegisterRecv();

    }

    // Close
    public void Disconnet()
    {
        if (Interlocked.Exchange(ref _disconnected, 1) == 1)
        {
            return;
        }

        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    // Send
    public void Send(byte[] sendBuff)
    {
        lock (_lock)
        {
            _sendQueue.Enqueue(sendBuff);
            if (_pendingList.Count == 0)
            {
                RegisterSend();
            }
        }
    }

    #region Network
    // Send
    void RegisterSend()
    {
        while(_sendQueue.Count > 0)
        {
            byte[] buff = _sendQueue.Dequeue();
            _pendingList.Add(new ArraySegment<byte>(buff, 0, buff.Length));
        }

        _sendArgs.BufferList = _pendingList;

        bool pending = _socket.SendAsync(_sendArgs);
        if (pending == false)
        {
            OnSendCompleted(null, _sendArgs);
        }
    }

    // Send
    void OnSendCompleted(object sender, SocketAsyncEventArgs args)
    {
        lock (_lock)
        {
            if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
            {
                try
                {
                    _sendArgs.BufferList = null;
                    _pendingList.Clear();

                    if (_sendQueue.Count > 0)
                    {
                        RegisterSend();
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine($"OnSendCompleted Failed {e}");
                }
            }
            else
            {
                Disconnet();
            }
        }
    }

    // Receive
    void RegisterRecv()
    {
        bool pending = _socket.ReceiveAsync(_recvArgs);
        if (pending == false)
        {
            OnRecvCompleted(null, _recvArgs);
        }
    }

    // Receive
    void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
    {
        if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                Console.WriteLine($"[From Client] {recvData}");

                RegisterRecv();
            }
            catch (Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
        }
        else
        {
            Disconnet();
        }
    }
    #endregion
}

Listener, Session - 엔진과 컨텐츠 코드 분리하기

class Listener
{
    // Socket
    Socket _listenSocket;
    Func<Session> _sessionFactory;
            
    public void Init(IPEndPoint endPoint, Func<Session> sessionFactory)
    {
        // Socket
        _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        _sessionFactory += sessionFactory;

        // Bind
        _listenSocket.Bind(endPoint);

        // Listen
        _listenSocket.Listen(10);

        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
        RegisterAccept(args);
    }

    void RegisterAccept(SocketAsyncEventArgs args)
    {
        args.AcceptSocket = null;

        bool pending = _listenSocket.AcceptAsync(args);
        if (pending == false)
        {
            OnAcceptCompleted(null, args);
        }
    }

    void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
    {
        if (args.SocketError == SocketError.Success)
        {
            Session session = _sessionFactory.Invoke();
            session.Start(args.AcceptSocket);
            session.OnConnected(args.AcceptSocket.RemoteEndPoint);
        }
        else
        {
            Console.WriteLine(args.SocketError.ToString());
        }

        RegisterAccept(args);
    }
}

abstract class Session
{
    Socket _socket;
    int _disconnected = 0;

    object _lock = new object();
    Queue<byte[]> _sendQueue = new Queue<byte[]>();
    List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
    SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
    SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();

    public abstract void OnConnected(EndPoint endPoint);
    public abstract void OnRecv(ArraySegment<byte> buffer);
    public abstract void OnSend(int numOfBytes);
    public abstract void OnDisconnected(EndPoint endPoint);

    public void Start(Socket socket)
    {
        _socket = socket;

        // Receive
        _recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
        _recvArgs.SetBuffer(new byte[1024], 0, 1024);

        // Send
        _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);

        RegisterRecv();
    }

    // Close
    public void Disconnect()
    {
        if (Interlocked.Exchange(ref _disconnected, 1) == 1)
        {
            return;
        }

        OnDisconnected(_socket.RemoteEndPoint);

        _socket.Shutdown(SocketShutdown.Both);
        _socket.Close();
    }

    // Send
    public void Send(byte[] sendBuff)
    {
        lock (_lock)
        {
            _sendQueue.Enqueue(sendBuff);
            if (_pendingList.Count == 0)
            {
                RegisterSend();
            }
        }
    }

    #region Network
    // Send
    void RegisterSend()
    {
        while(_sendQueue.Count > 0)
        {
            byte[] buff = _sendQueue.Dequeue();
            _pendingList.Add(new ArraySegment<byte>(buff, 0, buff.Length));
        }

        _sendArgs.BufferList = _pendingList;

        bool pending = _socket.SendAsync(_sendArgs);
        if (pending == false)
        {
            OnSendCompleted(null, _sendArgs);
        }
    }

    // Send
    void OnSendCompleted(object sender, SocketAsyncEventArgs args)
    {
        lock (_lock)
        {
            if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
            {
                try
                {
                    _sendArgs.BufferList = null;
                    _pendingList.Clear();

                    OnSend(_sendArgs.BytesTransferred);
                    if (_sendQueue.Count > 0)
                    {
                        RegisterSend();
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine($"OnSendCompleted Failed {e}");
                }
            }
            else
            {
                Disconnect();
            }
        }
    }

    // Receive
    void RegisterRecv()
    {
        bool pending = _socket.ReceiveAsync(_recvArgs);
        if (pending == false)
        {
            OnRecvCompleted(null, _recvArgs);
        }
    }

    // Receive
    void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
    {
        if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                OnRecv(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred));
                RegisterRecv();
            }
            catch (Exception e)
            {
                Console.WriteLine($"OnRecvCompleted Failed {e}");
            }
        }
        else
        {
            Disconnect();
        }
    }
    #endregion
}

class GameSession : Session
{
    public override void OnConnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnConnected: {endPoint}");

        byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
        Send(sendBuff);
        Thread.Sleep(1000);
        Disconnect();
    }

    public override void OnDisconnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnDisconnected: {endPoint}");
    }

    public override void OnRecv(ArraySegment<byte> buffer)
    {
        string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
        Console.WriteLine($"[From Client] {recvData}");
    }

    public override void OnSend(int numOfBytes)
    {
        Console.WriteLine($"Transferred bytes: {numOfBytes}");
    }
}

class Program
{
    static Listener _listener = new Listener();
    
    static void Main(string[] args)
    {
        // DNS
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddr = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

        _listener.Init(endPoint, () => { return new GameSession(); }) ;
        
        while (true)
        {
            ;
        }
    }
}

Connector - 클라에서 Session 만들기

// ServerCore -----------------------
public class Connector
{
    Func<Session> _sessionFactory;

    public void Connect(IPEndPoint endPoint, Func<Session> sessionFactory)
    {
        Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        _sessionFactory = sessionFactory;

        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.Completed += OnConnectCompleted;
        args.RemoteEndPoint = endPoint;
        args.UserToken = socket;

        RegisterConnect(args);
    }

    void RegisterConnect(SocketAsyncEventArgs args)
    {
        Socket socket = args.UserToken as Socket;
        if (socket == null)
            return;

        bool pending = socket.ConnectAsync(args);
        if (pending == false)
        {
            OnConnectCompleted(null, args);
        }
    }

    void OnConnectCompleted(object sender, SocketAsyncEventArgs args)
    {
        if (args.SocketError == SocketError.Success)
        {
            Session session = _sessionFactory.Invoke();
            session.Start(args.ConnectSocket);
            session.OnConnected(args.RemoteEndPoint);
        }
        else
        {
            Console.WriteLine($"OnConnectCompleted Fail: {args.SocketError}");
        }
    }
}

// Client-----------------------
class GameSession : Session
{
    public override void OnConnected(EndPoint endPoint)
    {
        // Send
        for (int i = 0; i < 10; i++)
        {
            byte[] sendBuff = Encoding.UTF8.GetBytes($"Hello World! {i}");
            Send(sendBuff);
        }
    }

    public override void OnDisconnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnDisconnected: {endPoint}");
    }

    public override void OnRecv(ArraySegment<byte> buffer)
    {
        string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
        Console.WriteLine($"[From Server] {recvData}");
    }

    public override void OnSend(int numOfBytes)
    {
        Console.WriteLine($"Transferred bytes: {numOfBytes}");
    }
}

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

        Connector connector = new Connector();
        connector.Connect(endPoint, () => { return new GameSession(); });

        while(true)
        {
            try
            {

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            Thread.Sleep(100);
        }
    }
}

240422_참조복사

Q Point가 구조체인데 tail 값이 복사가 아닌 참조로 들어가는 이유?

point가 클래스였다~

Q. 스왑 부분에서 값 복사만 일어나고 있는건가? 참조여서 안 될 것 같은데 왜 되는거지?

C#은 객체를 생성하면 자동으로 힙에 생성한다. 스택영역에서 데이터를 들고있을 때 힙주소만 들고 있다. 그래서 대입연산을 하면 얕은 복사로 주소만 복사해서 swap이 정상적으로 일어난다.

뱀게임

240423_Delegate

Delegate

함수 포인터 비슷하지만 다르다. 함수 주소값만 그대로 갖고있지 않고 객체이다.

함수를 변수로 갖고있을 수 있다.

+= 로 추가적으로 여러 함수를 갖고있을 수 있다.

Lamda

이벤트에 람다식으로 함수를 등록해서 쓴다

Func

return 있을 때

public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

Action

return 없을 때

public delegate void Action<in T>(T obj);

240424_json

Q 데이터를 어디서 넣어주는게 가장 적절할까?

  1. 객체가 생성될 때 생성자에서 해주기
  2. Init()에서 넣어주기
  3. DataLoader에서 아예 객체를 만들어서 Game()에 넘겨주기

등등 필요할 때 맞게 쓰면 된다

어떤 방법이든 상황에 잘 맞는다고 판단이 된다면 그 방법대로 한다. 다만, 어떻게 데이터를 넣어주는 것이 가장 가독성 및 유지보수 용이성이 좋고 효율적일지 항상 고민한다.

Q 유니티에서 생성자 안쓰는 이유?

MonoBehavior 클래스를 상속받는 클래스에서 생성자를 쓰면 안 된다. MonoBehavior 클래스를 상속받는 클래스는 new 키워드를 통해 인스턴스화하는 것을 유니티가 금지하고 있기 때문에 생성자 또한 사용하면 안된다.

MonoBehavior 클래스를 상속하는 것이 아닌 경우에는 생성자를 통해 초기화를 해주는 것이 전혀 문제가 없다

Q. 디버깅 잘하는 방법?

하나씩 브레이크포인트 찍고 확인하는 것이다. 코드를 작성하면서 의도한 대로 변수에 값이 담기고 메서드가 순서에 맞게 호출이 되는 것이 어느 지점까지인지, 어디서부터 어긋나는지를 찾는다.

Q Json 사용하는 방법?

Loader 만들어서 json파일에서 데이터 읽어서 객체로 던저준다. Serialize Deserialize로 데이터를 모으고 넣는다. 3번 방법으로 한다.

240425_Nullable

Json으로 데이터 저장하기

public DataLoader()
{
    dataPath = "../../../../Data/";
    options = new JsonSerializerOptions { WriteIndented = true };
}

public T LoadDataFromJson<T>(string fileName)
{
    string filePath = dataPath + fileName;
    string jsonString = File.ReadAllText(filePath);
    T objectByData = JsonSerializer.Deserialize<T>(jsonString);
    return objectByData;
}

public void SaveDataToJson<T>(T objectToWrite, string fileName)
{
    string filePath = dataPath + fileName;
    string jsonString = JsonSerializer.Serialize(objectToWrite, options);
    File.WriteAllText(filePath, jsonString);
}

Q 페이지 이동을 함수로 처리해서 콜스택이 계속 쌓인다

  1. Update 계속 돌려서 프레임마다 계속 글자 입력하게 하기 페이지 enum으로 만들어서 각 페이지이면 해당 내용 출력하기 - 오류 메시지 출력 부분도 일정 시간동안 나오도록 타이머 만들어야한다 - console.clear() 하면 깜박인다 더블 버퍼링 구현 필요
  2. 유니티 Scene처럼 Page 클래스 만들고 각 클래스에서 기능하도록 하기 - Game에 씬 스택으로 만들어서 관리하기 현재 페이지 top만 보여주기

구현 우선순위가 밀려서 나중에 수정하기로 했다.

Q 널 들어갈 수 있는 오브젝트 처리?

현재 장착한 장비 처리하면서 null을 넣어줘야 할 때가 있어서 nullable로 만들었다.

public Item? EquippedWeapon { get; set; }
public Item? EquippedArmor { get; set; }

240429_largestRectangleArea

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int max = 0;
        heights.push_back(0);
        stack<int> stack;
        int size = heights.size();

        stack.push(-1);
        for(int i = 0; i < size; i++){
            int h = heights[i];
            while(stack.top() != -1 && heights[stack.top()] >= h){
                int target = stack.top();
                stack.pop();

                int left = stack.top();
                int right = i;
                int width = (right - left - 1);
                int S = heights[target] * width;
                if(max < S){
                    max = S;
                }
            }  
            stack.push(i);
        }
        return max;
    }
};

0개의 댓글