Hi,
I'm working on a 3d MMORPG in C#. Anyway, I spent some time coming up with a very simple and high level messaging and serialization system (e.g. how do I serialize / deserialize my packets and send/receive them). Yes I did check out the FAQ and the thread mentioned there [grin].
Basically, I have one base class called 'GameMessage'. It has a constructor which takes a byte[] and deserialize it to the object, and a function called ToByte() which serializes it to a byte array. It uses serialization to do all the work.
Now all my messages simply inherit from this base class, and define their members and a constructor which just calls the base constructor, thats it! The beauty of this is that I DON'T have to write any serialization code now, all I have to do is inherit from the GameMessage and my CTOR and ToByte() will work as expected automatically.
This is really cool because making new messages is very simple.
Now I have 2 questions
1) How will this scale with potentially hundreds of players hitting a single World Server? Is the serialization going to choke and burn under much stress? I have to do some tests, but I'm curious if this whole idea is even feasable for a real time game.
2) Anything you would do differently? Some crazy reflection stuff that I'm not aware of that will make things easier or faster? One thing you'll notice I had to do was sort the FieldInfo class, because apparently GetFields() gives you the fields in a random order, not the order you define them.
Here's the code.. (base GameMessage class)
public class GameMessage
{
private static int SortFieldInfo(FieldInfo left, FieldInfo right)
{
if (left.Equals(right))
{
return 0;
}
if (right.Name == "ID")
return 1;
else if (right.FieldType.Name == "String")
return -1;
else
return 0;
}
public GameMessage(){}
public GameMessage(byte[] data)
{
List<byte> bytes = new List<byte>();
List<FieldInfo> info = new List<FieldInfo>(this.GetType().GetFields());
info.Sort(SortFieldInfo);
int idx = 0;
for (int i = 0; i < info.Count; i++)
{
if (info.FieldType.Name == "String")// is string)
{
string value;
UInt16 size;
if (i != info.Count - 1)
{
size = BitConverter.ToUInt16(data, idx);
idx += 2;
value = ASCIIEncoding.ASCII.GetString(data, idx, size);
idx += size;
}
else
{
value = ASCIIEncoding.ASCII.GetString(data, idx, data.Length - idx);
idx += data.Length - idx;
}
info.SetValue(this,value);
}
else
{
if (info.Name != "ID")
{
Type[] types = new Type[2] { data.GetType(), idx.GetType() };
object[] values = new object[2] { data, idx };
info.SetValue(this, typeof(BitConverter).GetMethod("To" + info.FieldType.Name, types).Invoke(null, values));
idx += Marshal.SizeOf(info.FieldType);
}
else
{
idx += sizeof(ID);
}
}
}
}
public byte[] ToByte()
{
List<byte> bytes = new List<byte>();
List<FieldInfo> info = new List<FieldInfo>(this.GetType().GetFields());
info.Sort(SortFieldInfo);
for(int i=0;i<info.Count;i++)
{
if (info.FieldType.Name == "String")
{
if (i != info.Count - 1)
{
bytes.AddRange(BitConverter.GetBytes(((UInt16)((string)info.GetValue(this)).Length)));
}
bytes.AddRange(ASCIIEncoding.ASCII.GetBytes((string)info.GetValue(this)));
}
else
{
bytes.AddRange((byte[])typeof(BitConverter).GetMethod("GetBytes", new Type[] { info.FieldType }).Invoke(null, new object[] { info.GetValue(this) }));
}
}
return bytes.ToArray();
}
}
Now here's 2 sample messages we define (this is how a World Server registers with the Match / 'Master server')
public class WSTOMSRegister : GameMessage
{
public const ID ID = 0;
public int WorldServerID;
public string Password;
public WSTOMSRegister(byte[] buffer) : base(buffer){}
public WSTOMSRegister(int worldServerID, string password) { this.WorldServerID = worldServerID;
this.Password = password; }
}
public class MSTOWSRegisterACK : GameMessage
{
public MSTOWSRegisterACK(byte[] buffer) : base(buffer) { }
public MSTOWSRegisterACK(bool result) {Result = result;}
public const ID ID = 1;
public bool Result;
}
Now this is how I send the message on the World Server, and process the ACK
matchServer.SendMessage(PacketHelper.GetNetMessage(new WSTOMSRegister(worldServerID,"omgwtfbbq")),NetChannel.Sequenced1);
NetMessage ack = null;
while ((ack = matchServer.ReadMessage()) == null)
{
matchServer.Heartbeat();
Thread.Sleep(0);
}
//get the ACK from the server
MSTOWSRegisterACK response = new MSTOWSRegisterACK(ack.ReadAllBytes());//PacketSerializer<MSTOWSRegisterACK>.DeSerialize(ack.ReadBytes());
this.isRegisteredWithMatchServer = response.Result;
this.running = response.Result;
This is how I process it on the Match Server
NetMessage msg = server.ReadMessage();
if (msg != null)
{
ID id = msg.ReadInt(); //TODO READ BYTES INTO A TEMPLATE FUNCTION TO CONVERT ID
switch (id)
{
//a world server wants to register with us
case WSTOMSRegister.ID:
{
WSTOMSRegister regRequest = new WSTOMSRegister(msg.ReadAllBytes());
bool valid = regRequest.Password == "omgwtfbbq";
server.SendMessage(PacketHelper.GetNetMessage(new MSTOWSRegisterACK(valid)), msg.Sender, NetChannel.Sequenced1);
if (valid)
{
WorldServer ws = new WorldServer(regRequest.WorldServerID);
WorldServerNode wsn = new WorldServerNode(msg.Sender,ws);// ws);
worldServerNodes.Add(wsn);
}
break;
}
}
}
Thanks for any suggestions!
[Edited by - graveyard filla on February 17, 2007 4:24:07 PM]