// 14003번 가장 긴 증가하는 부분 수열 5
/*
증가수열 만들기
각 dp에 저장한 idx 저장하기
뒤에서부터 출력하기
*/
// 27172번 수 나누기 게임
/*
그냥 다 돌리면서 나눠지는지 체크하기
- N^2으로 되어서 접근시간 줄이기 필요
*/
// 2166번 다각형의 면적
/*
센터 하나에서 점 두개 골라서 벡터 만들고 외적하기
넓이 계속 더하면서 전체 넓이 구하기
절댓값, /2하기
*/
레이어에서 현재 진행중인 애니메이션 갖고오기
몇 초 뒤 함수 실행
내부적으로 ThreadPool 사용
코드 재배치 막기
가시성
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);
}
}
}
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);
}
}
}
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);
}
}
}
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);
}
}
}
1 클릭할 때(카드 뒤집을 때), 시작할 때, 진행 중일 때 성공, 실패 소리 넣어보기
2 한 번씩 뒤집은 카드는 색을 다르게 표시하기 (옅은 회색 등)
3 카드 뒤집기에서 실제로 카드가 뒤집어지는 모습 연출하기
4 firstCard 고르고 5초 간 카운트 다운 - 안 고르면 다시 닫기
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;
}
}
}
카드 뒤집는 애니메이션 - 스크립트로 뒤집게 했는데 원하던 내용대로 실행되지 않았다. 유니티 라이프사이클에 대한 이해가 부족했다.
첫 번째 카드 고른 후 5초 뒤 닫기 - 코루틴으로 사용하면 Update에서 5초 돌면서 체크를 안해도 될 것 같다. 코루틴 사용법을 잘 몰라 일단 타이머로 구현했다.
카드 뒤집는 애니메이션 - Update에서 find로 현재 애니메이션 상태를 체크하는 코드를 넣었었다. Update는 매 프레임 돌아간다. find에서는 string으로 찾아서 비싸다. 이 부분을 없애기 위해서 작동하는 부분을 애니메이션에 넣었다. 더 깔끔하게 하려면 cardState별로 나누고 해당 애니메이션을 재생하는 방법을 생각했다. CardIdle, CardFlipping, CardFliped로 새로운 상태가 생기면 더 추가하면 된다. 하지만 카드의 애니메이션이 많지 않고 조건도 별로 없어서 하지 않았다.
에디터 prference에서 Genereate csproj 옵션을 다 켜놨다. Embeded랑 local만 킨다.
Filter by Selection 켜있었다. 클릭해서 끈다. 선택한 오브젝트에 적용되는 애니메이션만 표시해서 자식 오브젝트가 선택이 안됐다.
Lifecycle 관련된 문제이다. Update Animation Render 순서로 진행된다. Animator가 Update 앞에서 되도록 Animate Physics 로 설정하면 발생하지 않는다. 문제 해결은 코드가 아니라 애니메이션에서 카드를 뒤집도록 바꾸었다
코드를 저장해놓는 파일 인코딩 문제이다. utf-8로 변경한다. editorconfig파일 만들어서 utf-8로 전체 파일 저장하는 방식으로 설정한다. 다시 파일 열고 저장하면 바뀐다.
깃허브, 유니티 툴 사용 관련 문제가 있었다. 여러 사람이서 씬 배치를 하면 메타파일이 동시에 수정된다. pr을 했는데 하나하나 확인하면서 수정하기 힘들었다. 작업이 적어 작업 분배가 어려워서 기능별로 구분을 했다. 그래서 작업 부분이 겹쳤고 팀장님이 압축파일을 받아서 합치기로 했다. 그 이후부터 조금씩 추가하거나 수정했다. 합쳐진 버젼에 다시 내가 했던 작업물을 넣었다. 내 브랜치에서 메인 내용을 합치고 pr을 했다.
editorconfig를 추가하고 코드를 전부 utf-8로 바꾼 후에 pr를 했다. conflict나서 깃허브 웹으로 그 부분을 수정했다. svn에서는 이 블럭은 누가 한 걸로 선택하는지가 있는데 깃에서는 웹이나 커맨드로만 작업할 수 있는 것 같았다. 프로젝트 규모가 적당히 나눌만큼 된다면 문제가 줄어들 것 같다. 기능별로 작업 부분이 나뉜다. UI도 스크립트로 만들면 된다.
프로젝트 해보면서 해결해보는 경험이 좋았다. 강의 따라해보면서 하는 것보다 어떻게 개선할 수 있을까 고민하는 과정이 필요했다.
팀에서 할 일을 나누고 깃으로 공동작업을 했다. 내가 해야하는 작업은 작았다. 하지만 하나로 합치면서 시행착오를 거쳤다. 브랜치를 나누고 합치니까 각자 작업이 잘 보였다.
자신의 이름 얻고 IP주소 갖고오기
서버랑 통신할 소켓 생성, 서버의 IP 주소와 포트를 지정해서 연결 설정
소켓을 통해 서버에 메시지 전송
서버에서 받은 응답 수신, 화면에 출력
연결 종료
자신의 이름 얻고 대한 IP주소 갖고오기
클라 연결 받을 소켓 생성하기, 서버의 IP주소, 포트 바인딩
소켓으로 클라 연결 수신 대기하기
클라에서 메시지 받고 화면 출력
클라에게 메시지 보내기
연결 종료
각 프로세스가 가진 고유한 통신 채널
네트워크 패킷을 각 프로세스로 라우팅하는데 사용한다
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());
}
}
}
}
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());
}
}
}
}
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)
{
;
}
}
}
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)
{
;
}
}
}
서버에서 연결 수신, 새로운 세션 만들기
클라-서버 연결
데이터 송수신
연결 해제
클라에서 연결 수신, 새로운 세션 만들기
받는 데이터 순서대로 넣어서 하나씩 꺼내서 작업하기
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
}
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
}
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)
{
;
}
}
}
// 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);
}
}
}
point가 클래스였다~
C#은 객체를 생성하면 자동으로 힙에 생성한다. 스택영역에서 데이터를 들고있을 때 힙주소만 들고 있다. 그래서 대입연산을 하면 얕은 복사로 주소만 복사해서 swap이 정상적으로 일어난다.
함수 포인터 비슷하지만 다르다. 함수 주소값만 그대로 갖고있지 않고 객체이다.
함수를 변수로 갖고있을 수 있다.
+= 로 추가적으로 여러 함수를 갖고있을 수 있다.
이벤트에 람다식으로 함수를 등록해서 쓴다
return 있을 때
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
return 없을 때
public delegate void Action<in T>(T obj);
등등 필요할 때 맞게 쓰면 된다
어떤 방법이든 상황에 잘 맞는다고 판단이 된다면 그 방법대로 한다. 다만, 어떻게 데이터를 넣어주는 것이 가장 가독성 및 유지보수 용이성이 좋고 효율적일지 항상 고민한다.
MonoBehavior 클래스를 상속받는 클래스에서 생성자를 쓰면 안 된다. MonoBehavior 클래스를 상속받는 클래스는 new 키워드를 통해 인스턴스화하는 것을 유니티가 금지하고 있기 때문에 생성자 또한 사용하면 안된다.
MonoBehavior 클래스를 상속하는 것이 아닌 경우에는 생성자를 통해 초기화를 해주는 것이 전혀 문제가 없다
하나씩 브레이크포인트 찍고 확인하는 것이다. 코드를 작성하면서 의도한 대로 변수에 값이 담기고 메서드가 순서에 맞게 호출이 되는 것이 어느 지점까지인지, 어디서부터 어긋나는지를 찾는다.
Loader 만들어서 json파일에서 데이터 읽어서 객체로 던저준다. Serialize Deserialize로 데이터를 모으고 넣는다. 3번 방법으로 한다.
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);
}
구현 우선순위가 밀려서 나중에 수정하기로 했다.
현재 장착한 장비 처리하면서 null을 넣어줘야 할 때가 있어서 nullable로 만들었다.
public Item? EquippedWeapon { get; set; }
public Item? EquippedArmor { get; set; }
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;
}
};