4
\$\begingroup\$

I'm building an HTML5 massively multiplayer online game. I've been working on this project for awhile, but am having some trouble sorting through a couple of performance issues.

Since this is a multiplayer game, information is sent to and received from the network. When a client needs to know about new entities on the screen, the server must stream this data.

Unfortunately, one big problem I'm having right now is that sometimes either a) a lot of data needs to be streamed or b) lots of small pieces of data are streamed in a short amount of time. Both scenarios currently cause the client to drop rendering frames as the client spends its time receiving packets and processing the data.

What is the best way to prevent the networking data from causing frame rate problems? I am particularly interested in approaches to this kind of system that minimize frame drop issues, though suggestions tailored to my specific situation are welcome.

Update: To elaborate on the type of information sent in my particular case, my client does minimal interpolation and receives updates anytime the state of an entity changes on the server. The client also receives large packets with the entire state of an entity the first time the client needs to know about that entity.

\$\endgroup\$
7
  • \$\begingroup\$ Do you know if the framerate drop is a result of the network traffic itself, or is it a result of how you process the data? \$\endgroup\$ Commented Oct 13, 2014 at 22:21
  • \$\begingroup\$ @ChrisMills-Price In my particular case, I believe that the processing of the data is what causes the majority of frame drops. I can imagine scenarios, however, where drops might occur simply due to a flood of traffic. Interested in solutions for either issue. \$\endgroup\$
    – Chiubaka
    Commented Oct 15, 2014 at 1:37
  • 2
    \$\begingroup\$ 3 questions that come to mind: 1. Multithreading? 2. Do you need to process all the data that instant, or can you do some of it one frame, and then another portion of it the next? 3. Is everything being streamed from the server (eg, this rock fell exactly like this, and what happens will be streamed until it stops changing), or just enough for client-side to fill in the gaps (eg. a rock falls because of this force, rock is never mentioned again)? \$\endgroup\$ Commented Oct 15, 2014 at 2:00
  • 1
    \$\begingroup\$ Can you go into more detail about what the messages represent (motion updates?) and what's involved in processing them? Web workers could possibly be helpful... \$\endgroup\$ Commented Oct 15, 2014 at 2:46
  • \$\begingroup\$ I'll second Wolfgang Skyler's comment, particularly #2 & #3 since I suspect you're in a single-threaded environment. If the data processing is causing skipped frames, you'll need to either stretch processing over multiple frames (i.e. ensure time reserved each frame for the render) or re-evaluate what you're communicating between server and client to reduce total data processing. \$\endgroup\$ Commented Oct 15, 2014 at 18:24

5 Answers 5

7
+25
\$\begingroup\$

tl;dr You control how much data you are willing to process each frame. If a packet is too big, break it into smaller cells and process them one at a time (i.e one each frame). If you get a lot of small packets than split the group into chunks and limit the amount of information processing that is done each frame. The client does not need all the information; the server does so only send the client information that is crucial to the view. The rest could be handled on the server.

Handling resource intensive tasks

While maintaining user perceived performance


preface:


The issue:

You have a limited amount of resources to complete a certain task. You also wish to complete this task in a specific time frame (i.e 1/30 seconds). Using HTML5 does not allow you the same freedom and efficiency that you'd get with a native client. If you need more efficiency in utilizing resources you should seriously consider a native client or a plugin based Engine like Unity3D.

Possible sollutions:


1. Offloading client responsibilities to the server:

The client could be a dumb terminal in the extreme case, only accepting and uploading input from the user as in: player attacks Goblin Lord and downloading simple view related instructions from the server as in display kobold thief at (someX, someY) or play attack animation for Goblin[i] and when you go towards this pattern, the client does not need to receive much data.

2. Caching reusable data in advance:

You can store data on the client-side by uploading it during the game startup. If you know for instance that there are Kobolds and Goblins in your game, you can have the client download their stats and store them in memory in advance. Do not make their stats random, give them classes, levels or whatever modifiers you need but keep things organized and known in advance so you don't have to upload everything to the client on the fly. Instead send a small packet like Kobold Archer Lvl 11. Obviously using Enums for the actual code that is sent to the client to save more bandwidth.

3. Optimizing your code with a profiler:

Today it is a lot easier to optimize JS code with things like Chrome DevTools that can be used in Chrome and the insights it provides are more valuable than answers in an online Q&A website. It can help you dig into your code and see what is slowing it down. You may often find there is a loop you can trim down or a function you could optimize that is taking too much CPU resources. You can use something like PageSpeed Insights for Chrome. Look for a profiler suitable for your developer needs and you will find something useful.

4. Break a long task into chunks

If a party of 5 Goblins and 7 Kobolds suddenly appear, you don't have to process all their info during one frame. You could break it down and process one task each frame. This kind of optimization depends on the frequency in which intensive tasks appear. It is often unwise to attempt to process all the information at once and it results in spikes in performance. Design your code around that. Create an array of tasks and only perform as much as you can lazily evaluating certain things only when you have free resources or they absolutely needed.

You could buffer things up. If the player line of sight is 9 x 9 squares then have the client aware of a 18 x 18 square and slowly handle entities that are out of site in the buffer zone before it becomes critical (be careful that users could exploit that for an unfair advantage).

5. Compromise where it doesn't hurt gameplay experience

Use the framework you are working with to the extent possible performance wise. If you need to trim down some of the functionality or data in the game, consider it as an option. Sometimes you can group similar data and send it once instead of duplicating the work to lessen the amount of bandwidth and CPU used and other times you can eliminate the data altogether. For instance, who needs to know the Wisdom attribute of a skeleton? Or the Charisma of a Kobold? Sometimes excessive data could be eliminated or postponed. For instance who cares what items monsters are carrying unless they were successfully slain.

6. The real answer (low level):

Most of what we can offer in reply to a High-level question is patterns , theories and design principles. The real answer is often in the code, if we don't see or know your code we can help in a High-level way but someone needs to get down and dirty and actually inspect the code. Sometimes code is not properly optimized. Often the answer is to further optimize a few pieces of code and ask about them specifically. If you are not sure about something ask for a code review in the appropriate site, ask a friend or use the chat system here. You can even ask a question about a concise selection of code put in context here.

7. Design your game around the problem:

If the problem occurs when lots of entities appear at once, make the game a bit sparser with less mobs and make each mob slightly more powerful instead. If the problem occurs because of players character population density, make another town that attracts some of the players to lessen the amount of players in one area at a time. Give missions that take players to a uniformly random area in the map.

Think what and where the problem occurs and make that far less likely to occur by slightly modifying or tweaking the game mechanics. Remember that games needs to be scalable so you may need to make some compromises or educated decisions about what you place and where.

For any additional details or specifics please use the comments.

\$\endgroup\$
1
\$\begingroup\$

I assume that when your network data arrives, its processing diverts enough CPU power to slow down your rendering process.

Are you enforcing a constant framerate or are you just rendering frames as fast as you can?

Assuming you have a constant framerate, you may chose when your network packets are processed. I mean by that that your websocket event should do nothing except adding incoming messages into a waiting list, and somewhere in your game's main loop you process them.

It could go like that (very rough algorithm) :

while(playing) {
    render frame
    while (enough time until the next frame is due && messages waiting list is not empty) {
        remove first message from waiting list
        process this message
    }
}

This approach also allows you to prioritize your messages; if instead of having only one waiting messages list you have many, you can dispatch message depending of their priority.

That will allow you to process most important messages first, even if some less-important message has been received before them (i.e. if you have a lot of mobs attacking your player, you may delay for a second or two the chat related messages, or information stuffs like "xyzz has join the game" or "dfghf is now level 41").

If you don't enforce a constant framerate... well, I really think you should; the framerate perception will look better at a constant reasonable value (i.e. 30 fps) than if it varies depending of where you are or how many players are around you.

Another pointer to optimize may be to reduce the number of messages sent to clients. i.e. for non-critical events (like chat or informative messages), instead of sending a message every time someone says something you may consider limiting those to 1 message/second/player. The idea is when you have a message of such to send, you just wait for one second before sending it, and if another message of the same kind arrives during this second, you just add its contain to the first message payload.

Same thing for server issued world updates: Instead of sending one message with new coordinates for each player/actor, you may consider packing many of them (all if possible) in the same message. That shouldn't have any perceptible effect if you have a fixed framerate; you just have to send your world update message at the same rate (i.e. every 33ms if you have a 30 fps framerate on the client).

If possible, and assuming you use TCP (so no packet should be lost) you may keep on the server the last "world state" send for each client. That will allow you to only send what have changed since the last update.

This will also reduce network bandwidth you use which is always a good thing.

Bottom line: It's all about prioritization. You just cannot do everything at once, so you have to compromise and to process first what you think is the more important.

\$\endgroup\$
0
\$\begingroup\$

The client is written with JS? If so, than you need to use some kind of async library to make network IO in backgraound. There is RxJS and many other (try google: reactive, async etc.). Also consider switch from JS to CloujeScript: some local guys are using it for kind of tasks you've described.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I don't think the OP is doing synchronous IO; sounds like he's just observing a performance hit when processing results of async requests. \$\endgroup\$ Commented Oct 15, 2014 at 2:49
  • \$\begingroup\$ @Daniel That is correct. I am using WebSockets for I/O, and receiving data is basically an event on the client. \$\endgroup\$
    – Chiubaka
    Commented Oct 15, 2014 at 19:46
0
\$\begingroup\$

Run the game like any other, calculate data on the client side as needed and simply overwrite when you receive packets from the server.

\$\endgroup\$
0
\$\begingroup\$

You can also combine approaches from other answers (esp. splitting work into chunks, and finding bottlenecks in code) with implementing the most CPU-intensive tasks in asm.js - it's (a subset of) JavaScript, and it's fast on Fx and Chrome (and IE support is coming later).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .