Top 10 common pitfalls to avoid like the plague

This time we’re going to take a look at the top common errors and pitfalls that can be easily avoided when developing and deploying a multiplayer game with SmartFoxServer 2X. The list is compiled from a selection of topics that are quite popular in our support forums.

Knowing about these common pitfalls can possibly save you a significant amount of time and head scratching. Let’s see what they are.

1) Forgetting to use the SFSApi class

When developing server side code the SFSApi class is your best friend. It contains all of the most important methods you will ever need to implement your game logic: creating and joining rooms, setting variables, disconnecting users, sending messages, and tons more.

Additionally all of the SFSApi calls take care of updating clients and generating server-side events.

Bottom line: whenever you need to tell the server to perform an action, check this class before anything else, chances are you will find what you need.

The SFSApi class is super-easy to use as it is already instantiated behind the scenes in the main Extension class and all ClientRequest and ServerEvent handlers. You can access by calling the getApi() method in your code.

Additionally you can access the specialized API (BuddyList API, Game API) from this line of code:

SmartFoxServer.getInstance().getApiManager();

2) Manual database connection handling

This is likely the most popular problem recurring in the forums. When accessing the database connection directly, instead of using the SFSDBManager class, there is extra care required to properly dispose of it at the end of the code.

try
{
    connection = dbManager.getConnection();
    // Your database code goes here
}
catch(SQLException sqle)
{
    // Here we handle the SQL failure
}
finally
{
    try
    {
        connection.close();
    }
    catch(SQLException sqle)
    {
        // It shouldn't happen, but if it does it's best to leave a trace in the logs
    }
}

The example above should serve as a code template for manual connection handling. Inside the try block we start by obtaining the connection from SFS2X connection pool.

Since connections are reused to improve performance, we must make sure that they are returned to the pool when the operation is complete. Failing to do this will leak the connection, which in turn won’t be reusable. If this happens too often all available connections will be exhausted causing all sort of problems.

The finally block is particularly important because it is the last thing that is executed before leaving the method, even when an exception is thrown.

3) Sending data in for loops

If you find yourself writing code like this:

for (int i = 0; i < 100; i++)
{
	SFSObject obj = new SFSObject();
	obj.putParam("var" + i, i);

	sfs.Send(new ExtensionRequest("cmd", obj));
}

think again.

Writing data to the network inside a for-loop is a bad idea. In this example we’re generating a deluge of small packets, sent one after the other, while we could have simply packed all of the data in one object and send in one shot. Like this:

SFSObject obj = new SFSObject();

for (int i = 0; i < 100; i++)
{
	obj.putParam("var" + i, i);
}

sfs.Send(new ExtensionRequest("cmd", obj));

Additionally the first example is creating 100 SFSObjects, each having its 3 byte header and thus wasting an extra 300 bytes.

Bottom line: if your code sends data inside a for-loop, rewrite the code so that the send call is outside the loop and your data is aggregated in one request.

4) Enormous Room Lists

High traffic game servers can host tens of thousands of players and thousands of Game Rooms. If you plan to reach these heights it would be better to have a strategy for managing very big Room Lists.

If such a strategy is not in place clients will end up receiving ginormous lists of rooms that will waste lots of bandwidth and overwhelm the clients with data. Fortunately SFS2X provides a simple and efficient mechanism to organize Rooms into Groups and let users decide which of those they are interested in.

For more details on this topic we have a previous blog post, called Fine Tuning Room Lists.

5) Calling slow remote services

We have seen a number of projects where existing application API already exist in form of web services, often written in PHP and connected to an existing database. Typically these API handle a wide range of functionalities, from user profile management, credentials, integration with other website features etc..

Integrating these services with a SmartFoxServer 2X is relatively simple. All you need is to perform HTTP calls from your Extension code to the web server, wait for a response and finally reply to the client via socket.

On paper this looks like a great way to reuse existing services, without the hassle of rewriting them in Java, but unfortunately there is also a dark side to this approach which typically manifests in performance bottlenecks for SmartFoxServer: invoking remote HTTP services can be slow or even painfully slow, depending on several factors.

We’ve seen HTTP calls taking between 1000 and 8000 milliseconds to return, while SFS2X was receiving 100-200 calls per second. You can easily do the math and see that this is a scalability nightmare.

The most radical solution to this problem is avoiding it entirely, by accessing the data source (e.g. database) without intermediaries (HTTP server, PHP etc…) This can provide a speed bump of several orders of magnitude and thus solving the issue altogether.

Bottom line: If you have the option of choosing how to implement these services we absolutely positively recommend to access the datasource directly, you won’t regret it.

On the other hand, if this is not an option then we can only suggest to make sure that every step of the chain is carefully optimized, avoiding excessive lag between servers, improving the PHP performance with a compiler and checking the execution time of each query, optimizing those that don’t perform well enough.

6) Creating too many Task Schedulers

The Task Scheduler is an effective tool to run delayed operations such as timers and countdowns or generate interval based events. Schedulers are backed by a thread-pool so they can handle a multitude of tasks very efficiently.

It is important to note that these schedulers should be used parsimoniously as every new instance will create a new thread pool. We usually recommend to just create one Scheduler in your Zone and then reference it in all of your code, be it Zone or Room Extension.

Bottom line: the really is no reason to use more than one Task Scheduler per game/application. If you find that the Scheduler is not keeping up with the submitted work it’s just a matter of resizing its thread pool.

7) Using old, legacy collection classes

Quite surprisingly we have seen that a number of developers are still struggling with legacy collection classes such as Vector, HashTable and Dictionary. While these classes are not deprecated they are certainly not the first choice when it comes to collections in Java, these days.

The main issues with the legacy collection classes is their handling of concurrency. For instance, the Vector and HashTable classes synchronize every operation, making them unsuitable in multi-threaded environments such as SFS2X.

Since Java 5 there has been a significant progress in the choice of efficient collections, especially in respect to concurrency. Classes like ConcurrentHashMap, CopyOnWriteArrayList and its Set counterpart offer a much better implementation and performance.

To learn more we highly recommend this article.

8) Sending an unrealistic number of packets per second (pps)

While in principle it is tempting to send an update every time a frame is rendered, we must realize that the network poses physical limits to the number of packets per second that can be sent. This value is referred to as pps and it can vary based on the type of transport protocol in use (e.g. TCP vs UDP), the network conditions, the physical distance between client and server etc…

Under normal network conditions it is usually assumed that TCP can send ~15-20 pps while UDP can reach ~30, even 40 pps, although the latter is also subject to packet loss.

With these numbers in mind, and considering that not all players can enjoy a fast internet connection, we can quickly realize that sending an update on every game frame is not going to work, especially if the frame rate runs at 30 or more frames.

Bottom line: there is no need to link the network updates to the frame rate. If the server (or client) is generating updates at a rate faster than the one at which is sending, it is common practice to simply buffer the updates and send them at specific intervals.

9) Sending XML/JSON json data instead of optimized SFSObjects

Sometimes a game loads data from another resource in a specific format such as text, maybe formatted as JSON or XML. Typical examples are level data, or user data coming from an external data source.

There is nothing bad in sending text data to the client or viceversa if this is not a recurring type of message. However, if you find yourself often sending XML/JSON-type data you should keep in mind that this is going to waste bandwidth and it would be best to unpack it from the original format and pack it back into SFSObjects using the specific data types. (short vs int vs long etc…)

In other words a JSON packet like this:

SFSObject sfso = new SFSObject();
sfso.putUtfString("data", "{'x':15.4456, 'y':81.7099, 'z':-0.0003, 'active':true}");

can be packed more efficiently using native types:

SFSObject sfso = new SFSObject();
sfso.putFloat("x", 15.4456);
sfso.putFloat("y", 81.7099);
sfso.putFloat("z", -0.0003);
sfso.putBool("ac", false);

and the latter will result in roughly 1/2 the size of the former.

For recurring messages this can make an important difference.

10) Live testing

Live testing for multiplayer games is fundamental. It is roughly the equivalent of having someone taste your cakes before you open a bakery and start mass production. If there are problems with the recipe or the final results it is best to know in advance, than having angry paying customers at the door complaining for what they have bought.

Developing and testing multiplayer games in a local environment doesn’t guarantee that the game will behave correctly and doesn’t take into account all of the variables that exist when the product is finally live.

It may not come as a surprise that it is highly important to allocate enough time for live beta testing, especially using external testers that can try the game from a number of different devices and connection and report back any issue.

While it seems we’re stating the obvious it has happened many times that clients reported problems with their game because they went from development to production directly.

11) (bonus) Don’t buy the server license the day your game goes live!

This is a bonus item that we had to include in this top-10 list, as it is a very simple pitfall to avoid.

As per the title we really recommend to avoid that! If you’re going to buy a license don’t wait just a few hours before the game goes live. Payments sometimes don’t go through and we need a bit of time to process the order and deliver the license(s).

If you’re doing this you’ve possibly also fallen in pitfall #10 and we can’t emphasize more how these two are so important to avoid!

Happy coding.