C# Server ๋งŒ๋“ค๊ธฐ
2013. 06. 07.
์ตœ์žฌ์˜
Why?
โ€ข ๋นŒ๋“œ ์†๋„
โ€ข ํ‘œํ˜„๋ ฅ
2
async
await
extension
method
linq
Observable
TPL
DynamicObject
Reflection
Attribute
IEnumerable
ํ๋ฆ„
3
Network
Datasheet
Database
Logic
async, await
TaskCompletionSource
Reflection
Attribute
Dynamic
XmlLinq
IEnumerable
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 */);
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;
}
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;
}
async, await
7
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๋ฅผ ์ฐพ์•„ ์ˆ˜ํ–‰ํ•จ
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
Summary
โ€ข Socket์˜ Async ๊ณ„์—ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉ (.NET ๋‚ด๋ถ€๋Š” IOCP๋กœ ์ฒ˜๋ฆฌ)
โ€ข async, await์€ TaskContinuation์˜ Syntax sugar
โ€ข async, await Keyword๋กœ Callback ์—†์ด ํŽธํ•˜๊ฒŒ Network ์ฝ”๋“œ ์ž‘์„ฑ
โ€ข ๊ทธ๋Ÿฌ๋ฉด์„œ๋„ Thread Pool์— ์˜ํ•œ ํšจ์œจ์ ์œผ๋กœ ์ˆ˜ํ–‰๋จ
(.NET Thread Pool๋„ ๋‚ด๋ถ€์—์„œ IOCP๋กœ ๊ด€๋ฆฌ)
9
Network
Datasheet
โ€ข dynamic์„ ์‚ฌ์šฉํ•œ ์ผ๋ฐ˜์ ์ธ Xml ์ฝ๊ธฐ
โ€ข code generator๋ฅผ ์‚ฌ์šฉ
10
Xml ์ž‘์„ฑ
Xml Model
๊ตฌํ˜„
Xml Parser
๊ตฌํ˜„
์ž๋™ ์ƒ์„ฑ ์ž๋™ ์ƒ์„ฑ
code generator
DynamicObject
XmlDefinition (์ž๋™ ์ƒ์„ฑ)
general-loader
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);
}
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;
Summary
โ€ข dynamic์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋”ฉ ์‹œ๊ฐ„ ๋‹จ์ถ•(Model, Parser ์ž‘์„ฑ ๋ถˆํ•„์š”)
โ€ข ์˜คํƒ€๋กœ ์ธํ•œ ์ ‘๊ทผ ์œ„๋ฐ˜์€ Runtime์— ํ™•์ธ ๊ฐ€๋Šฅ
โ€ข XmlDefinition์ด ํ•„์š”ํ•จ(์ž๋™ ์ƒ์„ฑ ๊ฐ€๋Šฅ)
โ€ข ๋ณด๋‹ค ๋น ๋ฅธ ์†๋„๋ฅผ ์›ํ•  ๊ฒฝ์šฐ์—๋Š” Model, Parser๋ฅผ Generate
(IVsSingleFileGenerator)
13
Datasheet
Database
โ€ข Reflection๊ณผ Attribute ์‚ฌ์šฉ์œผ๋กœ ์ผ๋ฐ˜์ ์ธ Bind ๊ตฌํ˜„
โ€ข scheme ์ž‘์—…์ด ๋ถˆํ•„์š”ํ•  ๊ฒฝ์šฐ model ์ž‘์„ฑ๋งŒ์œผ๋กœ ๋ชจ๋“  ๊ตฌํ˜„ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ
14
Scheme
์ž‘์„ฑ
DataModel
์ž‘์„ฑ
Bind ๊ตฌํ˜„
nosql or generator Reflection
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๋กœ
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์„ ์ฝ”๋“œ์— ๊ธฐ๋ก
Summary
โ€ข model ๊ฐ์ฒด์˜ type ์ •๋ณด๋ฅผ ์ตœ๋Œ€ํ•œ ์‚ฌ์šฉ
โ€ข Attribute๋ฅผ ๋ถ€์—ฌํ•˜์—ฌ ๊ฐ€๋Šฅํ•œ ๋งŽ์€ ์ •๋ณด๋ฅผ ์ฝ”๋“œ์— ์ฃผ์ž…
(DSL, ๋ฌธ์„œ, ์ฃผ์„ ๋“ฑ ์™ธ๋ถ€ ์ •๋ณด๋Š” ์ถ”๊ฐ€ ์œ ์ง€ ๋ณด์ˆ˜๊ฐ€ ํ•„์š”ํ•จ)
โ€ข Dirty๋‚˜ Lazy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ตœ์ ํ™” ๊ฐ€๋Šฅ
โ€ข ์—ญ์‹œ ๋ณด๋‹ค ๋น ๋ฅธ ์†๋„๋ฅผ ์›ํ•  ๊ฒฝ์šฐ์—๋Š” Code Generate๋ฅผ ์‚ฌ์šฉ
(partial class๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ฝ”๋“œ์™€ ํ˜ผํ•ฉ ๊ฐ€๋Šฅ)
17
Database
Logic
โ€ข yield return์„ ์‚ฌ์šฉํ•˜์—ฌ State Machine ์ œ๊ฑฐ
โ€ข context ์œ ์ง€๋ฅผ ์œ„ํ•œ ๋ณ„๋„ ์ฝ”๋”ฉ์ด ํ•„์š” ์—†์Œ
18
ํ–‰๋™
A
ํ–‰๋™
B
ํ–‰๋™
C
1์ดˆ ๋’ค
3์ดˆ ๋’ค
5์ดˆ ๋’ค
Logic
Engine
Logic #1 Logic #2
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;
}
์ œ์–ด๊ถŒ์ด ํ˜ธ์ถœ์ž์—๊ฒŒ ๋„˜์–ด๊ฐ
๋‹ค์Œ ํ˜ธ์ถœ ์‹œ ์ด ์ง€์ ๋ถ€ํ„ฐ ์ˆ˜ํ–‰
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()์„ ๋ณด๋‹ค ์ž‘์€ ๋‹จ์œ„๋กœ ์ˆ˜ํ–‰
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๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ
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๋ฅผ ๋ฐ˜ํ™˜
์ˆ˜ํ–‰์ด ์™„๋ฃŒ๋œ ๋กœ์ง ์‚ญ์ œ
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
Summary
โ€ข async, await์„ ์‚ฌ์šฉํ•œ ๋™๊ธฐ์  Network(IO) ํ”„๋กœ๊ทธ๋ž˜๋ฐ
โ€ข dynamic์„ ์‚ฌ์šฉํ•œ Runtime type dispatch
โ€ข Reflection, Attribute๋ฅผ ์‚ฌ์šฉํ•œ boiler plate ์ฝ”๋“œ ์ค„์ด๊ธฐ
โ€ข Attribute๋กœ metadata๋ฅผ ์ฝ”๋“œ๋กœ ๊ธฐ๋กํ•˜์—ฌ ์œ ์ง€ ๋ณด์ˆ˜ ๋น„์šฉ ์ค„์ด๊ธฐ
โ€ข coroutine์„ ์‚ฌ์šฉํ•œ ๋™๊ธฐ์  ๋กœ์ง ํ”„๋กœ๊ทธ๋ž˜๋ฐ
24
๋ณด๋‹ค ์ ์€, ๊ทธ๋ฆฌ๊ณ  ์ง๊ด€์ ์ธ(๋™๊ธฐ์ ) ์ฝ”๋”ฉ์œผ๋กœ ์œ ์ง€ ๋ณด์ˆ˜ ๋น„์šฉ ์ค„์ด๊ธฐ
๋†’์€ ํ‘œํ˜„๋ ฅ
์„ฑ๋Šฅ ๋ฌธ์ œ๋Š” ํšŒ๋กœ์˜ ๋ฐœ์ „์ด ํ•ด๊ฒฐํ•ด ์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค [...]
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

C# Game Server

  • 1.
    C# Server ๋งŒ๋“ค๊ธฐ 2013.06. 07. ์ตœ์žฌ์˜
  • 2.
    Why? โ€ข ๋นŒ๋“œ ์†๋„ โ€ขํ‘œํ˜„๋ ฅ 2 async await extension method linq Observable TPL DynamicObject Reflection Attribute IEnumerable
  • 3.
  • 4.
    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 */);
  • 5.
    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; }
  • 6.
    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; }
  • 7.
    async, await 7 Network async voidReceiveLoop(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๋ฅผ ์ฐพ์•„ ์ˆ˜ํ–‰ํ•จ
  • 8.
    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
  • 9.
    Summary โ€ข Socket์˜ Async๊ณ„์—ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉ (.NET ๋‚ด๋ถ€๋Š” IOCP๋กœ ์ฒ˜๋ฆฌ) โ€ข async, await์€ TaskContinuation์˜ Syntax sugar โ€ข async, await Keyword๋กœ Callback ์—†์ด ํŽธํ•˜๊ฒŒ Network ์ฝ”๋“œ ์ž‘์„ฑ โ€ข ๊ทธ๋Ÿฌ๋ฉด์„œ๋„ Thread Pool์— ์˜ํ•œ ํšจ์œจ์ ์œผ๋กœ ์ˆ˜ํ–‰๋จ (.NET Thread Pool๋„ ๋‚ด๋ถ€์—์„œ IOCP๋กœ ๊ด€๋ฆฌ) 9 Network
  • 10.
    Datasheet โ€ข dynamic์„ ์‚ฌ์šฉํ•œ์ผ๋ฐ˜์ ์ธ Xml ์ฝ๊ธฐ โ€ข code generator๋ฅผ ์‚ฌ์šฉ 10 Xml ์ž‘์„ฑ Xml Model ๊ตฌํ˜„ Xml Parser ๊ตฌํ˜„ ์ž๋™ ์ƒ์„ฑ ์ž๋™ ์ƒ์„ฑ code generator DynamicObject XmlDefinition (์ž๋™ ์ƒ์„ฑ) general-loader
  • 11.
    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); }
  • 12.
    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;
  • 13.
    Summary โ€ข dynamic์„ ์‚ฌ์šฉํ•˜์—ฌ์ฝ”๋”ฉ ์‹œ๊ฐ„ ๋‹จ์ถ•(Model, Parser ์ž‘์„ฑ ๋ถˆํ•„์š”) โ€ข ์˜คํƒ€๋กœ ์ธํ•œ ์ ‘๊ทผ ์œ„๋ฐ˜์€ Runtime์— ํ™•์ธ ๊ฐ€๋Šฅ โ€ข XmlDefinition์ด ํ•„์š”ํ•จ(์ž๋™ ์ƒ์„ฑ ๊ฐ€๋Šฅ) โ€ข ๋ณด๋‹ค ๋น ๋ฅธ ์†๋„๋ฅผ ์›ํ•  ๊ฒฝ์šฐ์—๋Š” Model, Parser๋ฅผ Generate (IVsSingleFileGenerator) 13 Datasheet
  • 14.
    Database โ€ข Reflection๊ณผ Attribute์‚ฌ์šฉ์œผ๋กœ ์ผ๋ฐ˜์ ์ธ Bind ๊ตฌํ˜„ โ€ข scheme ์ž‘์—…์ด ๋ถˆํ•„์š”ํ•  ๊ฒฝ์šฐ model ์ž‘์„ฑ๋งŒ์œผ๋กœ ๋ชจ๋“  ๊ตฌํ˜„ ํ•ด๊ฒฐ ๊ฐ€๋Šฅ 14 Scheme ์ž‘์„ฑ DataModel ์ž‘์„ฑ Bind ๊ตฌํ˜„ nosql or generator Reflection
  • 15.
    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๋กœ
  • 16.
    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์„ ์ฝ”๋“œ์— ๊ธฐ๋ก
  • 17.
    Summary โ€ข model ๊ฐ์ฒด์˜type ์ •๋ณด๋ฅผ ์ตœ๋Œ€ํ•œ ์‚ฌ์šฉ โ€ข Attribute๋ฅผ ๋ถ€์—ฌํ•˜์—ฌ ๊ฐ€๋Šฅํ•œ ๋งŽ์€ ์ •๋ณด๋ฅผ ์ฝ”๋“œ์— ์ฃผ์ž… (DSL, ๋ฌธ์„œ, ์ฃผ์„ ๋“ฑ ์™ธ๋ถ€ ์ •๋ณด๋Š” ์ถ”๊ฐ€ ์œ ์ง€ ๋ณด์ˆ˜๊ฐ€ ํ•„์š”ํ•จ) โ€ข Dirty๋‚˜ Lazy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ตœ์ ํ™” ๊ฐ€๋Šฅ โ€ข ์—ญ์‹œ ๋ณด๋‹ค ๋น ๋ฅธ ์†๋„๋ฅผ ์›ํ•  ๊ฒฝ์šฐ์—๋Š” Code Generate๋ฅผ ์‚ฌ์šฉ (partial class๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ฝ”๋“œ์™€ ํ˜ผํ•ฉ ๊ฐ€๋Šฅ) 17 Database
  • 18.
    Logic โ€ข yield return์„์‚ฌ์šฉํ•˜์—ฌ State Machine ์ œ๊ฑฐ โ€ข context ์œ ์ง€๋ฅผ ์œ„ํ•œ ๋ณ„๋„ ์ฝ”๋”ฉ์ด ํ•„์š” ์—†์Œ 18 ํ–‰๋™ A ํ–‰๋™ B ํ–‰๋™ C 1์ดˆ ๋’ค 3์ดˆ ๋’ค 5์ดˆ ๋’ค Logic Engine Logic #1 Logic #2
  • 19.
    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; } ์ œ์–ด๊ถŒ์ด ํ˜ธ์ถœ์ž์—๊ฒŒ ๋„˜์–ด๊ฐ ๋‹ค์Œ ํ˜ธ์ถœ ์‹œ ์ด ์ง€์ ๋ถ€ํ„ฐ ์ˆ˜ํ–‰
  • 20.
    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()์„ ๋ณด๋‹ค ์ž‘์€ ๋‹จ์œ„๋กœ ์ˆ˜ํ–‰
  • 21.
    LogicEngine (Coroutine) 21 Logic public voidEntryLoop() { 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๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ
  • 22.
    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๋ฅผ ๋ฐ˜ํ™˜ ์ˆ˜ํ–‰์ด ์™„๋ฃŒ๋œ ๋กœ์ง ์‚ญ์ œ
  • 23.
    Summary โ€ข IEnumerable๊ณผ yieldreturn์˜ ์กฐํ•ฉ์œผ๋กœ coroutine ๊ตฌํ˜„ โ€ข ๊ฐ„๋‹จํ•œ coroutine์ด์ง€๋งŒ ๋งŽ์€ boiler plate ์ฝ”๋“œ ์ž‘์„ฑ ํšŒํ”ผ ๊ฐ€๋Šฅ โ€ข yield return์œผ๋กœ ๋งŽ์€ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ๋‹ค์–‘ํ•œ ํ™œ์šฉ (Unity3D Engine) โ€ข ์—ฌ๋Ÿฌ Thread๊ฐ€ LogicEngine์„ ์ˆ˜ํ–‰ํ•˜์—ฌ Entry ์ˆ˜ํ–‰ ๋ถ„์‚ฐ ๊ฐ€๋Šฅ (์—ฌ๋Ÿฌ Thread๊ฐ€ ์ˆ˜ํ–‰ํ•  ๊ฒฝ์šฐ Lfe ๋“ฑ ๊ฐ์ฒด๋ณ„ ์ˆ˜ํ–‰ ๋™๊ธฐํ™” ๊ณ ๋ ค๊ฐ€ ํ•„์š”ํ•จ) โ€ข Script๋ฅผ C#์œผ๋กœ ์ž‘์„ฑ ์‹œ ๋„์›€์ด ๋  ๋“ฏ(?) (c# script + roslyn + linqpad + nuget) 23 Logic
  • 24.
    Summary โ€ข async, await์„์‚ฌ์šฉํ•œ ๋™๊ธฐ์  Network(IO) ํ”„๋กœ๊ทธ๋ž˜๋ฐ โ€ข dynamic์„ ์‚ฌ์šฉํ•œ Runtime type dispatch โ€ข Reflection, Attribute๋ฅผ ์‚ฌ์šฉํ•œ boiler plate ์ฝ”๋“œ ์ค„์ด๊ธฐ โ€ข Attribute๋กœ metadata๋ฅผ ์ฝ”๋“œ๋กœ ๊ธฐ๋กํ•˜์—ฌ ์œ ์ง€ ๋ณด์ˆ˜ ๋น„์šฉ ์ค„์ด๊ธฐ โ€ข coroutine์„ ์‚ฌ์šฉํ•œ ๋™๊ธฐ์  ๋กœ์ง ํ”„๋กœ๊ทธ๋ž˜๋ฐ 24 ๋ณด๋‹ค ์ ์€, ๊ทธ๋ฆฌ๊ณ  ์ง๊ด€์ ์ธ(๋™๊ธฐ์ ) ์ฝ”๋”ฉ์œผ๋กœ ์œ ์ง€ ๋ณด์ˆ˜ ๋น„์šฉ ์ค„์ด๊ธฐ ๋†’์€ ํ‘œํ˜„๋ ฅ ์„ฑ๋Šฅ ๋ฌธ์ œ๋Š” ํšŒ๋กœ์˜ ๋ฐœ์ „์ด ํ•ด๊ฒฐํ•ด ์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค [...]
  • 25.
    How much fasteris 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