SFS2X Docs / ExtensionsJS / advanced-concepts
» JavaScript Extensions: advanced concepts
In this section we're going to discuss advanced topics regarding how JavaScript Extensions work behind the scenes, performance and integration with the Java runtime.
- Mixing Java and JavaScript
- Performance tuning
- Multi threading and concurrency
- Frequently asked questions
» Mixing Java and JavaScript
As we have mentioned earlier, JavaScript Extensions run under the Java Nashorn engine, which in turn can interact with the Java runtime and access of all of the JDK API. This means we can easily instantiate and work with any Java library available to us, and integrate our code with non JavaScript dependencies.
Here's an example of JavaScript/Java interaction using Java collections:
var HashSet = Java.type('java.util.HashSet'); var hset = new HashSet(); hset.add("Kermit"); hset.add("Piggy"); hset.add("Gonzo"); hset.add("Gonzo"); hset.add("Gonzo"); print(hset + ", Size: " + hset.size());
Which returns:
[Kermit, Gonzo, Piggy], Size: 3
Essentially the "trick" to import and use Java classes is handled by the Java.type() function. Alternatively we can also reference the fully qualified name of the class we want to use.
var hset = new java.util.HashSet();
This immediately opens the door for all sorts of possibilities. For example we could write our own JDBC code to access a database bypassing SFS2X default API:
function accessJDBC() { var dbMan = getParentZone().getDBManager(); var connection = dbMan.getConnection(); try { var userName = "Kermit"; var stmt = connection.prepareStatement("SELECT pword,id FROM muppets WHERE name=?"); stmt.setString(1, userName); var result = stmt.executeQuery(); if (!result.first()) trace("Name was not found in database: " + userName); else trace("User: " + userName + " found!"); } catch(ex) { trace("Error reading from database: " + ex.getMessage()); } finally { try { connection.close(); } catch(ex2) { trace("Error while shutting down connection: " + ex2.getMessage()); } } }
Other opportunities to integrate the Java API in your JavaScript code could be using more advanced collections than what JavaScript offers, such as List, Set, Queue, Map, TreeMap, etc.
For a full, in-depth coverage on the Nashorn engine and its inner workings we highly recommend these external resources:
» Performance tuning
The Nashorn engine is a major improvement over the previous Mozilla's Rhino in terms of performance, but still JavaScript is no Java when it comes to execution speed.
What kind of performance difference (compared to native Java) you could expect is highly dependent on the type of code you're running, but the dynamic nature of JavaScript is essentially what determines the difference.
For example a classic benchmark such as calculating the "Sieve of Eratosthenes" (up to 100'000) takes an average of 0.5ms in Java and around 5.75ms in JavaScript (both running on a quad core Intel i7, warming up the JVM with 100 runs). Even though the performance difference is major (10x in favor of Java) this is likely a worst case scenario since simpler code that doesn't involve lots of math will likely run with a smaller speed gap.
In case you found yourself dealing with code running tight loops and lots of calculations (e.g. physics, pathfinding, etc) you may consider moving the code to a compiled Java class and importing it in your JavaScript code.
» Memory usage
When running hundreds or thousands of Room each with an attached JavaScript Extension you will notice a significant increase in memory usage. This is because every Extension must instantiate a separate JavaScript engine which uses a significant amount of memory.
To provide a rough idea, for 2000 Rooms you will likely need 3GB of available RAM and if you plan to run more an 8GB RAM server is highly recommended.
A possible optimization to drastically reduce RAM impact is to avoid attaching an Extension to every Room and instead using a centralized Zone Extension to deal with every game's request. While this may require a little bit more coding, it can significantly improve the server performance, especially if running on low hardware specifications.
» Schedulers and Threads
When running multiple Task Schedulers in every Room it is possible to degrade the server performance due to the high number of threads that are created.
Let's imagine a game where every Room Extension instantiates a Scheduler with 2 threads. With 2000 Rooms we will end up with 4000 active threads. This is quite a lot and will likely cause performance issues, since every thread allocates RAM for it's stack space and adds to the JVM's task switching process.
To alleviate this issue it is highly recommendable to create a centralized Task Scheduler in the main Zone Extension and have every Room Extension use it, instead of creating thousands of them. With this approach we are not forced to scale the number of threads linearly with the number of Rooms, which is never necessary, and we can fine tune the Scheduler's thread pool more precisely.
» Multi threading and concurrency
Since JavaScript doesn't provide any native support for threads, there is no way to handle concurrency other than using the tools offered by Java. Also none of the native types in JS (numbers, strings, arrays, objects, etc) are thread safe. For this reason JavaScript Extension run fully synchronized all the time.
What this means is that any call to a JavaScript Extension acquires a mutually exclusive lock for the entire time of its execution, thus forcing the server to run JS code serially rather than in parallel as it happens in Java. To put it in an even different way, concurrent calls to the same Extension will not run in parallel as it happens in Java.
As a last resort you can actually tell the JavaScript Extension to run in thread-unsafe mode, by calling the following global method in your init() method:
function init() { setThreadSafe(false); }
However when you do this you will have to replace all collections with Java's thread safe versions and use locking semantics to deal with proper synchronization of concurrent code.
If you find yourself needing to do this, we would recommend considering using Java Extensions instead, where you can have full control of all concurrency aspects.
» Frequently asked questions
» Can I integrate the XYZ JavaScript library in my code?
It depends. The Nashorn JS engine provides full support for ECMAScript 5 but it doesn't offer the typical API you find in browsers such as DOM, WebGL, Canvas, etc. This means that if the library doesn't depend on browser specific features you will be certainly able to import it and compile it in your Extension.
» Can I integrate the XZY Java library in my code?
Yes, any Java library can be added to the SFS2X classpath and used from JavaScript as explained in the previous section of this document. We recommend deploying the library under the SFS2X/extensions/__lib__/ folder so it can be accessed by any Java or JavaScript Extension.
» Can I use a minifier for my server side code?
While in theory you could do it, in practice it's really not a good idea for multiple reasons:
- Minifiers are useful to reduce the size of JavaScript code downloaded via browser. Server side Extensions are never downloaded. They reside on the server side and are only executed there, which renders minification moot.
- Minification will make your exceptions harder to parse while developing because all code formatting is lost.
- Minification doesn't produce performance improvements.
There are no advantages in minifying Extension code and it causes additional work and problems when debugging.
» Can I debug JavaScript code natively?
The short answer is NO. There isn't a simple way to plug a debugger directly into your JavaScript code as you would do with Java. Our suggestion is to debug by logging all the necessary information so that you can trace what is going on.
It is also worth mentioning that IntelliJ IDEA provides a Nashorn debugger, which we haven't tried or are familiar with; but you may want to investigate if you think it's necessary.
» How come my Extension doesn't reload when I save one of its modules?
The auto-reload function in SmartFoxServer 2X monitors the folder specified for your Extension and reloads your code on the fly. However, if you have created modules in sub folders, these will not trigger the reload. You can still make it work by saving any of the files in your main fodler.
Alternatively if you need auto-reload for all your files we recommend keeping all your modules in the main Extension directory.