3

I've been all over google looking for an answer to my problem, hoping someone here can shed some light on the whole thing.

Okay, so, I'm working on a 2D Online RPG (Role Playing Game. Think Final Fantasy, but online). I've been working on it for awhile, but I've run into a bit of a problem. Now, I'm not 100% sure that compression will fix the issue, or if simply the loop is too large.

Note: I'm using the Lidgren Networking library. Open to suggestions on other options, as Lidgren doesn't support compression.

Some information on what I'm trying to do:

I'm trying to send a packet that contains all the information for the map. The smaller the map, the faster the packet goes, but when I try to send a 250x250 map (top down, tile based), it takes several minutes to do.

I have 2 theories on this. One is that it's taking awhile because the maps are pretty big in size at that point (around 5MB), the other is that the maps are so large, the loop itself is taking awhile.

The maps contain strings, bytes, int32s, int16s, etc. I have a compression method that requires a byte array, but not 100% sure on how to go about converting all the data to a byte array (I've only been working with C# for a little over a year, now, and I'm entirely self taught).

The loop is pretty large, all things considered. There are 4 tile layers, all of which need to be looped through. This is what leads me to believe that the loop might be the problem. When starting the server, it caches all the maps (meaning it builds the packets before they're ever needed), so it doesn't seem to be an issue when sending the map when the client needs it. It's pretty quick, in all honesty.

But when you edit the map and send it to be saved, it can take quite a bit of time for it to send the packet to the server and the server to send the updated map packet back to all the clients currently logged in and standing on that map. A little too much time, to be honest. I figured maybe compressing the packet would help, but as I've stated, I'm not 100% sure that's the problem.

Here's the loop that's in use:

public static void cacheMap(Int32 mapNum)
    {            
        int X; int Y;
        int MaxX = Types.Map[mapNum].MaxX;
        int MaxY = Types.Map[mapNum].MaxY;

        // ****** PreAllocate Buffer ******
        int mapSize = Marshal.SizeOf(Types.Map[mapNum]);
        int tileSize = Marshal.SizeOf(Types.Map[mapNum].Tile[0,0]);
        int nLength = mapSize + ((tileSize * MaxX) * MaxY);

        NetOutgoingMessage TempBuffer = ServerTCP.sSock.CreateMessage(nLength);

        // Must Preallocate //
        TempBuffer.Write(mapNum);

        // ****** Map Info ******
        TempBuffer.Write(Types.Map[mapNum].Name);            
        TempBuffer.Write(Types.Map[mapNum].Music);            
        TempBuffer.Write(Types.Map[mapNum].Revision);
        TempBuffer.Write(Types.Map[mapNum].Moral);
        TempBuffer.Write(Types.Map[mapNum].Weather);
        TempBuffer.Write(Types.Map[mapNum].Tileset);
        TempBuffer.Write(Types.Map[mapNum].Up);
        TempBuffer.Write(Types.Map[mapNum].Down);
        TempBuffer.Write(Types.Map[mapNum].Left);
        TempBuffer.Write(Types.Map[mapNum].Right);
        TempBuffer.Write(Types.Map[mapNum].BootMap);
        TempBuffer.Write(Types.Map[mapNum].BootX);
        TempBuffer.Write(Types.Map[mapNum].BootY);
        TempBuffer.Write(Types.Map[mapNum].MaxX);
        TempBuffer.Write(Types.Map[mapNum].MaxY);            

        // ****** Tiles ******
        for (X = 0; X <= MaxX - 1; X++)
        {
            for (Y = 0; Y <= MaxY - 1; Y++)
            {
                for (int I = 0; I <= 4; I++)
                {
                    TempBuffer.Write(Types.Map[mapNum].Tile[X, Y].Layer[I].X);
                    TempBuffer.Write(Types.Map[mapNum].Tile[X, Y].Layer[I].Y);
                    TempBuffer.Write(Types.Map[mapNum].Tile[X, Y].Layer[I].Tileset);
                    TempBuffer.Write(Types.Map[mapNum].Tile[X, Y].Autotile[I]);
                }
                TempBuffer.Write(Types.Map[mapNum].Tile[X, Y].Type);
                TempBuffer.Write(Types.Map[mapNum].Tile[X, Y].Data1);
                TempBuffer.Write(Types.Map[mapNum].Tile[X, Y].Data2);
                TempBuffer.Write(Types.Map[mapNum].Tile[X, Y].Data3);
                TempBuffer.Write(Types.Map[mapNum].Tile[X, Y].DirBlock);
                Application.DoEvents();
            }
        }

        // ****** Send Map SoundID ******
        for (X = 0; X <= MaxX - 1; X++)
        {
            for (Y = 0; Y <= MaxY - 1; Y++)
            {
                TempBuffer.Write(Types.Map[mapNum].SoundID[X, Y]);
            }
        }

        TempBuffer.Write(Types.Map[mapNum].Instanced);

        Types.MapCache[mapNum].Data = TempBuffer;
    }

Any help that can be provided would be greatly appreciated. If you need me to provide more information, I can do that.

Perfekt
  • 31
  • 3
  • 2
    That's way too much data to send over network every frame. Send just the changes instead, and you'll be able to run over dialup! :) And by the way, the most expensive thing in your loop is the `Application.DoEvents` by far. – Luaan Nov 23 '15 at 18:50
  • For non gamers add some detail. A lot people are not going to know what an RPG is. – paparazzo Nov 23 '15 at 18:51
  • gamedev.stackexchange.com may be better place. – Alexei Levenkov Nov 23 '15 at 18:55
  • 2D RPG is a rocket propelled grenade make out of paper, of course. @Frisbee – MicroVirus Nov 23 '15 at 18:55
  • My apologies. The problem is, generally, the entire map has been edited and needs to be sent when saving it. I know it's a lot of data, which is the problem I'm having. There's only one map in the game this large, all the others are 50x50 to 100x100 and the time it takes to send them isn't very bad. Appreciate all the comments. I'll edit my post with a bit more information specifying that it's for a Role Playing Game. – Perfekt Nov 23 '15 at 19:01
  • You should time your method to generate the cache map, without the `DoEvents`; I'm guessing it'll run pretty fast. If it runs too slow, then add the `DoEvents` more carefully, such as every so many iterations only and not for each inner iteration. It's probably the network send that takes long. At 200 kb/s, it will still take 30 seconds to send out the 5 mb data. – MicroVirus Nov 23 '15 at 19:02
  • Also, the Application.Doevents line is only used in caching the map, which is used when the server is initially loaded. The other packets are exactly the same, minus that line, though. – Perfekt Nov 23 '15 at 19:04
  • Have you timed that method? I'd say that it should return probably within a second or so without the `DoEvents`. – MicroVirus Nov 23 '15 at 19:04
  • Compression can definitely help you cut down on that size and this would be valuable, but depending on the data it might not get you enough. Then you need to start thinking of a way of sending the data in parts so you can do other things while the data is being sent. – MicroVirus Nov 23 '15 at 19:05
  • I removed that line and the server started much faster, so thanks for that. As I said, it's not used when sending the savemap packet. With a 250x250 map it takes around 5 minutes to build the packet, send it, receive it, update the cache, and send it back. I know this is far longer than it should be taking, but not 100% sure on what the exact problem is. – Perfekt Nov 23 '15 at 19:07
  • Sending out 5 MiB of data just takes long; most connections don't have a fast upload speed, so you might only be getting 100-200 KiB/s, so that would take 25-50 seconds. If the server then has to send out the map to multiple clients, then this time multiplies by the number of clients. – MicroVirus Nov 23 '15 at 19:09
  • If this is server code (which you said it was) then you might not need DoEvents -- DoEvents should just be needed for UI updates. In addition it makes sense to implement a server program as a "console" this will take out all the overhead of graphical UI. – Hogan Nov 23 '15 at 19:10
  • That's why I was thinking compression might help. I'm just not entirely sure how to go about compressing the packet, because Lidgren doesn't support compression directly. Using GZip, I could store it all in a byte array, compress it, then write the compressed byte array to the buffer and send that, but I don't really know how to convert the packet to a byte array. I've looked into it, but everything I've found hasn't been any help. – Perfekt Nov 23 '15 at 19:11
  • If you are sending this update to multiple clients then you probably want to make a threaded model that allows you send to all of them asynchronously. Right now it seems you have to wait to finish sending to all clients. – Hogan Nov 23 '15 at 19:12
  • I plan on getting around to that, but right now, I'm having the problem with it taking forever to update just one client. Currently running a test to time the packet and it's already been going for 4 minutes, now. – Perfekt Nov 23 '15 at 19:14
  • And be aware network routers will compress segments anyway so you are already getting some compression. Compressed data does not compress as much as uncompressed data. – paparazzo Nov 23 '15 at 19:20
  • You can use a `MemoryStream` along with a `BinaryWriter` over that memory stream to get it into byte array form with only minimal changes to your current code. You are actually doing something quite like this already in using the `NetOutgoingMessage` buffer, which also supports `Write`s. – MicroVirus Nov 23 '15 at 19:21
  • Much appreciated, guys! Thanks for all the help and suggestions. – Perfekt Nov 23 '15 at 19:46
  • 1
    What are the types of X,Y etc? Consider using smaller data types (0-255 only? use a byte, 0-65535? use a UInt16... etc) Maybe some of your data can be reduced to enums? Lidgren supports writing arbitrary number of bits; so if your number can only be values 0 to 7 you only need to write 4 bits. – lidgren Nov 23 '15 at 22:57
  • 1
    I was able to fix the problem (takes about 10-15 seconds to send a 250x250 map now) by preallocating the packet before sending it. I know it shows that I did so in the above code, but I apparently forgot it in the packet the client sends to the server. Thanks for the help, everyone! Some suggestions you guys offered have sped things up and cleaned my code up a bit and I very much appreciate that. – Perfekt Nov 28 '15 at 23:31
  • Just found this post and started with the 3rd part network library so was interested in this. Can you write your answer up and select it as the answer for others to see? Are you basically saying you forgot to do the below in the clients or just forgot the nLength in the brackets? >>> NetOutgoingMessage TempBuffer = ServerTCP.sSock.CreateMessage(nLength); – Harag Jul 06 '16 at 12:10

0 Answers0