PDL.xml에서 한줄씩 가져와서 줄 부터 작업을 하는데
두번째 줄을 가져오면 memberType은 long이 될 것이고, 그에 따라 PacketFormat에서 정의한 포맷대로 {숫자}에 인자로 전달해준 변수들을 집어넣어 하나의 문자열로 만들고 각 ~Code string 변수에 넣어 코드를 대신 작성해주는 코드이다.
"public long playerId"
"this.playerId = BitConverter.ToInt64(s.Slice(pos, s.Length - pos));\r\npos += sizeof(long);"
"success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.playerId);\r\ncount += sizeof(long);"
이렇게 하면 xml 파일로 패킷만 정의해주면
PacketGenerator를 통해 코드를 자동 생성해줄 수 있다.
<?xml version="1.0" encoding="utf-8" ?>
<PDL>
<packet name="PlayerInfoReq">
<long name="playerId"/>
<string name="name"/>
<list name="skill">
<int name="id"/>
<short name="level"/>
<float name="duration"/>
</list>
</packet>
</PDL>
class PacketFormat
{
// {0} : 패킷이름
// {1} : 멤버 변수들
// {2} : 멤버 변수 Read
// {3} : 멤버변수 Write
public static string packetFormat =
@"
class {0}
{{
{1}
public void Read(ArraySegment<byte> segment)
{{
ushort pos = 0;
ReadOnlySpan<byte> s = new ReadOnlySpan<byte>(segment.Array, segment.Offset, segment.Count);
pos += sizeof(ushort);
pos += sizeof(ushort);
{2}
}}
public ArraySegment<byte> Write()
{{
ArraySegment<byte> segment = SendBufferHelper.Open(4096); // SendBuffer의 _buffer
ushort size = 0;
bool success = true;
Span<byte> s = new Span<byte>(segment.Array, segment.Offset, segment.Count);
size += sizeof(ushort); // 2 : 패킷 size 사이즈
success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), (ushort)PacketID.{0}); // Slice가 Span<byte> 만들어서 반환하면 packetId를 그 범위에 바이트로 변환해서 씀
size += sizeof(ushort); // packetId 사이즈
{3}
success &= BitConverter.TryWriteBytes(s, size);
if (success == false)
return null; // 반환해서 밖에서 null체크
return SendBufferHelper.Close(size); // 최종 패킷 크기
}}
}}
";
// {0} 변수 형식
// {1} 변수 이름
public static string memberFormat =
@"public {0} {1};";
// {0} 리스트 이름 [대문자]
// {1} 리스트 이름 [소문자]
// {2} 멤버 변수들
// {3} 멤버 변수 Read
// {4} 멤버 변수 Write
public static string memberListFormat =
@"public class {0}
{{
{2}
public void Read(ReadOnlySpan<byte> s, ref ushort count)
{{
{3}
}}
public bool Write(Span<byte> s, ref ushort count)
{{
bool success = true;
{4}
return success;
}}
}}
public List<{0}> {1}s = new List<{0}>();";
// {0} : 변수 이름
// {1} : To~ 변수 형식
// {2} : 변수 형식
public static string readFormat =
@"this.{0} = BitConverter.{1}(s.Slice(pos, s.Length - pos));
pos += sizeof({2});";
// {0} 변수 이름
public static string readStringFormat =
@"ushort {0}Len = BitConverter.ToUInt16(s.Slice(count, s.Length - count));
count += sizeof(ushort);
this.{0} = Encoding.Unicode.GetString(s.Slice(count, {0}Len));
count += {0}Len;";
// {0} 리스트 이름 [대문자]
// {1} 리스트 이름 [소문자]
public static string readListFormat =
@"this.{1}s.Clear();
ushort {1}Len = BitConverter.ToUInt16(s.Slice(count, s.Length - count));
count += sizeof(ushort);
for (int i = 0; i < {1}Len; i++)
{{
{0} {1} = new {0}();
{1}.Read(s, ref count);
{1}s.Add({1});
}}";
// {0} 변수 이름
// {1} 변수 형식
public static string writeFormat =
@"success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.{0});
count += sizeof({1});";
// {0} 변수 이름
public static string writeStringFormat =
@"ushort {0}Len = (ushort)Encoding.Unicode.GetBytes(this.{0}, 0, this.{0}.Length, segment.Array, segment.Offset + count + sizeof(ushort));
success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), {0}Len);
count += sizeof(ushort);
count += {0}Len;";
// {0} 리스트 이름 [대문자]
// {1} 리스트 이름 [소문자]
public static string writeListFormat =
@"success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), (ushort)this.{1}s.Count);
count += sizeof(ushort);
foreach ({0} {1} in this.{1}s)
success &= {1}.Write(s, ref count);";
}
class Program
{
static string genPackets;
static void Main(string[] args)
{
XmlReaderSettings settings = new XmlReaderSettings()
{
IgnoreComments = true,
IgnoreWhitespace = true
};
using (XmlReader r = XmlReader.Create("PDL.xml", settings))
{
r.MoveToContent();
while(r.Read())
{
if (r.Depth == 1 && r.NodeType == XmlNodeType.Element)
ParsePacket(r);
// Console.WriteLine(r.Name + " " + r["name"]);
}
File.WriteAllText("GenPackets.cs", genPackets);
}
}
public static void ParsePacket(XmlReader r)
{
if (r.NodeType == XmlNodeType.EndElement)
return;
if (r.Name.ToLower() != "packet")
{
Console.WriteLine("Packet 노드 invaild");
return;
}
string packetName = r["name"];
if(string.IsNullOrEmpty(packetName))
{
Console.WriteLine("Packet 이름 없음");
return;
}
Tuple<string, string, string> t = ParseMembers(r);
genPackets += string.Format(PacketFormat.packetFormat,
packetName, t.Item1, t.Item2, t.Item3);
}
// {1} : 멤버 변수들
// {2} : 멤버 변수 Read
// {3} : 멤버변수 Write
public static Tuple<string,string,string> ParseMembers(XmlReader r)
{
string packetName = r["name"];
string memberCode = "";
string readCode = "";
string writeCode = "";
int depth = r.Depth + 1;
while(r.Read())
{
if (r.Depth != depth)
break;
string memberName = r["name"];
if(string.IsNullOrEmpty(memberName))
{
Console.WriteLine("member 이름 없음");
return null;
}
if (string.IsNullOrEmpty(memberCode) == false)
memberCode += Environment.NewLine; // 엔터 붙여줌
if (string.IsNullOrEmpty(readCode) == false)
readCode += Environment.NewLine;
if (string.IsNullOrEmpty(writeCode) == false)
writeCode += Environment.NewLine;
string memberType = r.Name.ToLower();
switch(memberType)
{
case "bool":
case "byte":
case "short":
case "ushort":
case "int":
case "long":
case "float":
case "double":
memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
readCode += string.Format(PacketFormat.readFormat, memberName, ToMemberType(memberType), memberType);
writeCode += string.Format(PacketFormat.writeFormat, memberName, memberType);
break;
case "string":
memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
readCode += string.Format(PacketFormat.readStringFormat, memberName);
writeCode += string.Format(PacketFormat.writeStringFormat, memberName);
break;
case "list":
Tuple<string, string, string> t = ParseList(r);
memberCode += t.Item1;
readCode += t.Item2;
writeCode += t.Item3;
break;
default:
break;
}
}
memberCode = memberCode.Replace("\n", "\n\t");
readCode = readCode.Replace("\n", "\n\t\t");
writeCode = writeCode.Replace("\n", "\n\t\t");
return new Tuple<string, string, string>(memberCode, readCode, writeCode);
}
public static Tuple<string, string,string> ParseList(XmlReader r)
{
string listName = r["name"];
if(string.IsNullOrEmpty(listName))
{
Console.WriteLine("리스트 이름 없음");
return null;
}
Tuple<string, string, string> t = ParseMembers(r);
string memberCode = string.Format(PacketFormat.memberListFormat,
FirstCharToUpper(listName),
FirstCharToLower(listName),
t.Item1,
t.Item2,
t.Item3);
string readCode = string.Format(PacketFormat.readListFormat,
FirstCharToUpper(listName),
FirstCharToLower(listName));
string writeCode = string.Format(PacketFormat.writeListFormat,
FirstCharToUpper(listName),
FirstCharToLower(listName));
return new Tuple<string,string,string>(memberCode, readCode,writeCode);
}
public static string ToMemberType(string memberType)
{
switch(memberType)
{
case "bool":
return "ToBoolean";
case "short":
return "ToInt16";
case "ushort":
return "ToUInt16";
case "int":
return "ToInt32";
case "long":
return "ToInt64";
case "float":
return "ToSingle";
case "double":
return "ToDouble";
default:
return "";
}
}
public static string FirstCharToUpper(string input)
{
if (string.IsNullOrEmpty(input))
return "";
return input[0].ToString().ToUpper() + input.Substring(1);
}
public static string FirstCharToLower(string input)
{
if (string.IsNullOrEmpty(input))
return "";
return input[0].ToString().ToLower() + input.Substring(1);
}
}