25
C# Server 만들기 2013. 06. 07. 최재영

C# Game Server

Embed Size (px)

DESCRIPTION

implements game server using c# features such as async, await, dynamic, linq, reflection, attribute, ienumerable.

Citation preview

Page 1: C# Game Server

C# Server 만들기

2013. 06. 07.

최재영

Page 2: C# Game Server

Why?

• 빌드속도

• 표현력

2

asyncawait

extension method

linq

Observable

TPL

DynamicObject

Reflection

Attribute

IEnumerable

Page 3: C# Game Server

흐름3

Network

Datasheet

Database

Logic

async, awaitTaskCompletionSource

ReflectionAttribute

DynamicXmlLinq

IEnumerable

Page 4: C# Game Server

Network

• 빠른패킷 처리를위해 비동기 IO 사용

4

var socket = new Socket(AddressFamily.InterNetwork,

SocketType.Stream, ProtocolType.IPv4);

// preprocess socket

var buffer = new byte[4096];

socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None,

result => {

var received = socket.EndReceive(result);

// process packet

}, null /* state */);

Page 5: C# Game Server

TaskCompletionSource

• C++ Future + C# Task Awaitable Future

5

Network

static Task<int> ReceiveAsync(this Socket sock, byte[] buf, int off, int size)

{

var source = new TaskCompletionSource<int>(sock);

sock.BeginReceive(buf, off, size, SocketFlags.None, state =>

{

try

{

source.SetResult(socket.EndReceive(state));

}

catch (Exception e)

{

source.SetException(e);

}

}, source);

return source.Task;

}

Page 6: C# Game Server

async, await

• Task.Result 비동기대기(await), 그런코드가있는함수(async)

6

Network

static async Task<byte[]> ReceiveAsync(this Socket socket, int count)

{

var buffer = new byte[count];

var length = 0;

do

{

var num = await ReceiveAsync(socket, buffer, length, count);

if (num == 0)

break;

length += num;

count -= num;

} while (count > 0);

if (length != buffer.Length) throw new IOException("packet is truncated.");

return buffer;

}

Page 7: C# Game Server

async, await7

Network

async void ReceiveLoop(Socket socket)

{

while (true)

{

var lengthBytes = await socket.ReceiveAsync(sizeof (int));

var packetBytes = await socket.ReceiveAsync(

BitConverter.ToInt32(lengthBytes, 0));

// process packet

var packet = ReadPacket(packetBytes);

_handlerMap[packet.GetType()](packet);

}

}

await하는 지점에 아직 IO signal이 없다면, 해당 Task는 잠시 멈추고,

가용한 다른 Task를 찾아 수행함

Page 8: C# Game Server

Listener (Server)

• ClientSocket을비동기로 Accept해서,

• 각 Socket마다비동기로 Packet을대기해서처리함

8

Network

var listener = new Socket(AddressFamily.InterNetwork,

SocketType.Stream, ProtocolType.Tcp);

var localEndPoint = new IPEndPoint(IPAddress.Any, Port);

listener.Bind(localEndPoint);

listener.Listen(100);

while (true)

{

var clientSocket = await listener.AcceptAsync();

ReceiveLoop(clientSocket);

}async method

Page 9: C# Game Server

Summary

• Socket의 Async 계열 함수를사용 (.NET 내부는 IOCP로 처리)

• async, await은 TaskContinuation의 Syntax sugar

• async, await Keyword로 Callback 없이 편하게 Network 코드작성

• 그러면서도 Thread Pool에 의한효율적으로수행됨

(.NET Thread Pool도내부에서 IOCP로 관리)

9

Network

Page 10: C# Game Server

Datasheet

• dynamic을사용한일반적인 Xml 읽기

• code generator를사용

10

Xml 작성Xml Model

구현Xml Parser

구현

자동 생성 자동 생성

code generator

DynamicObject

XmlDefinition (자동 생성)

general-loader

Page 11: C# Game Server

DynamicObject

• RuntimeType으로동적으로멤버접근 가능

• 올바른 Type의값을 미리준비해야하므로 XmlDefinition 필요

11

Datasheet

public class XmlObject : DynamicObject

{

private readonly Dictionary<string, object> _attributes;

private readonly Dictionary<string, IEnumerable<XmlObject>>

_multipleChildren;

private readonly Dictionary<string, XmlObject> _singleChildren;

public override bool TryGetMember(GetMemberBinder binder,

out object result)

{

return TryGetValue(_attributes, binder.Name, out result) ||

TryGetValue(_singleChildren, binder.Name, out result) ||

TryGetValue(_multipleChildren, binder.Name, out result);

}

Page 12: C# Game Server

DynamicObject

• Attribute를읽을때 Type 변환을미리수행

• dynamic으로접근하여 Model 없이접근가능

12

Datasheet

_attributes = element.Attributes.OfType<XmlAttribute>()

.ToDictionary(e => e.Name,

e => defNode.SelectAttribute(e.Name)

.ReadValue(e.Value));

<?xml version="1.0" encoding="utf-8" ?>

<World>

<Config port="40123"/>

</World>

World.Config@port : int

dynamic world = XmlObject.Load("World.xml", _def);

_listener.Port = world.Config.port;

Page 13: C# Game Server

Summary

• dynamic을사용하여코딩시간단축(Model, Parser 작성불필요)

• 오타로인한접근위반은 Runtime에확인가능

• XmlDefinition이 필요함(자동생성 가능)

• 보다빠른 속도를원할 경우에는 Model, Parser를 Generate

(IVsSingleFileGenerator)

13

Datasheet

Page 14: C# Game Server

Database

• Reflection과 Attribute 사용으로일반적인 Bind 구현

• scheme 작업이불필요할경우 model 작성만으로모든구현해결 가능

14

Scheme 작성

DataModel 작성

Bind 구현

nosql or generator Reflection

Page 15: C# Game Server

Reflection

• Runtime에 model의 type정보로 scheme를구축

• 각 데이터의 Serialize/Deserialize 구현필요 (String과 object 상호 변환)

• 모든 Model 객체를 Xml로 변환

15

Database

new XElement("Objects",

_gameObjects.Values.Select(

obj =>

new XElement("Object",

obj.GetType().GetProperties()

.Where(e => e.CanRead && e.CanWrite)

.Select(e => new XAttribute(e.Name,

SerializeValue(e.PropertyType, e.GetValue(obj, null))

)))));

모든 Property에 대해 출력

출력할 때에는 string으로, 읽을 때에는 다시 object로

Page 16: C# Game Server

Attribute

• Runtime에접근가능한 metadata를코드에주입

16

Database

[CommandHandler("npc", "새로운 Npc를 생성합니다")]

internal bool SpawnNpc(Entity admin,

[CommandArgument("Npc의 이름")] string npcName,

[CommandArgument("Npc의 X 위치", 0)] double newX,

[CommandArgument("Npc의 Y 위치", 0)] double newY)

{

if (!admin.Has<Pos>())

return false;

var npc = EntityManager.Instance.Create(EntityTemplate.Ids.Npc);

npc.Get<Motion>().Dir = admin.Get<Motion>().Dir;

npc.Get<Nameplate>().Name = npcName;

npc.Get<Pos>().Assign(new Pos {X = newX, Y = newY});

명령어와 설명을 코드에 기록

인자 설명과 기본 값, type을 코드에 기록

Page 17: C# Game Server

Summary

• model 객체의 type 정보를최대한사용

• Attribute를부여하여가능한많은 정보를코드에주입

(DSL, 문서, 주석 등 외부 정보는 추가 유지보수가 필요함)

• Dirty나 Lazy를사용하여최적화가능

• 역시보다 빠른속도를원할경우에는 Code Generate를사용

(partial class를사용하여사용자 코드와혼합 가능)

17

Database

Page 18: C# Game Server

Logic

• yield return을 사용하여 State Machine 제거

• context 유지를 위한 별도 코딩이 필요 없음

18

행동A

행동B

행동C

1초 뒤

3초 뒤

5초 뒤LogicEngine

Logic #1 Logic #2

Page 19: C# Game Server

IEnumerable (Coroutine)

• IEnumerable을반환 type으로설정하여 yield return 사용

• 다음로직 수행까지의대기시간을반환

19

Logic

public IEnumerable<int> RegenerateEntry()

{

while (true)

{

var newNpc = _context.NewGameObject(ObjectType.Npc);

_context.AddGameObject(newNpc);

_context.BroadcastPacket(newNpc.ToSpawnPacket());

var newAi = new EachAi(this, newNpc);

_context.AddEntry(newAi.AiLogicEntry);

var nextInterval = _random.Next(interval) + base;

yield return nextInterval;

}제어권이 호출자에게 넘어감

다음 호출 시 이 지점부터 수행

Page 20: C# Game Server

IEnumerator (Coroutine)

• IEnumerator의 MoveNext() 함수로코드 실행

• Current로 yield return 결과 값 확인

20

Logic

var enumerator = RegenerateEntry().GetEnumerator();

while (enumerator.MoveNext())

{

Thread.Sleep(enumerator.Current);

}

IEnumerable로부터 IEnumerator를 가져옴

MoveNext()로 yield 사이 구간 코드 수행

yield return 반환 값을 얻음. 대기 시간만큼 쉼

여러 IEnumerator를 관리하고 Thread.Sleep()을 보다 작은 단위로 수행

Page 21: C# Game Server

LogicEngine (Coroutine)21

Logic

public void EntryLoop()

{

var prev = DateTime.Now;

while (true)

{

var now = DateTime.Now;

var delta = (now - prev).Milliseconds;

foreach (var newOne in _newLogicEntries)

{

var newEntry = new LogicEntry

{

Enumerator = newOne().GetEnumerator(),

SleepTime = 0

};

_logicEntries.Add(newEntry);

}

_newLogicEntries.Clear();

새로 추가된 Entry로부터

IEnumerator객체를 생성

Page 22: C# Game Server

LogicEngine (Coroutine)22

Logic

var removals = new List<LogicEntry>();

foreach (var each in _logicEntries)

{

each.SleepTime -= delta;

if (each.SleepTime >= 0)

continue;

if (!each.Enumerator.MoveNext())

removals.Add(each);

else each.SleepTime = each.Enumerator.Current;

}

_logicEntries.RemoveAll(removals.Contains);

prev = now;

const int logicInterval = 16;

Thread.Sleep(logicInterval);

}

}

수행 가능한 IEnumerator 집합

수행할 시간이 된 로직을 찾아서 실행

로직 함수가 return되어 완료되면 MoveNext()가 fasle를 반환

수행이 완료된 로직 삭제

Page 23: C# Game Server

Summary

• IEnumerable과 yield return의조합으로 coroutine 구현

• 간단한 coroutine이지만많은 boiler plate 코드작성회피 가능

• yield return으로많은 정보를전달하여다양한활용 (Unity3D Engine)

• 여러 Thread가 LogicEngine을수행하여 Entry 수행분산 가능

(여러 Thread가수행할경우 Lfe 등 객체별 수행 동기화고려가 필요함)

• Script를 C#으로 작성 시 도움이될 듯(?)

(c# script + roslyn + linqpad + nuget)

23

Logic

Page 24: C# Game Server

Summary

• async, await을사용한동기적 Network(IO) 프로그래밍

• dynamic을사용한 Runtime type dispatch

• Reflection, Attribute를사용한 boiler plate 코드줄이기

• Attribute로 metadata를코드로기록하여유지보수 비용줄이기

• coroutine을사용한동기적로직프로그래밍

24

보다 적은, 그리고 직관적인(동기적) 코딩으로 유지 보수 비용 줄이기

높은 표현력

성능 문제는 회로의 발전이 해결해 줄 것입니다 [...]

Page 25: C# Game Server

How much faster is C++ than C#?25

C# may not be faster, but it makes

YOU/ME faster. That's the most

important measure for what I do. :)

http://stackoverflow.com/questions/138361/how-much-faster-is-c-than-c