OpenAphid-Engine

JavaScript&OpenGL ES Powered 2D Game Engine for Native iOS/Android Apps

Part I: How to Choose a JavaScript Engine for iOS and Android Development

DISCLAIMER: the post contains my personal opinions on the subject. I would appreciate it if you could correct my mistakes.

Back to the time when I started OpenAphid-Engine, there were already several similar iOS/Android projects. These projects, either commercial or open source, expose their core features by JavaScript language. For instance, Titanium and PhoneGap allow developers to use JavaScript to build native iOS/Android apps; ngCore enables building cross platform games by pure JavaScript. JavaScript language has been chosen as a first-class citizen as it’s one of the most popular programming language. It eases the learning curve and easily attracts developers into a new product ecosystem.

How to Support JavaScript on iOS/Android

There are two main approaches to support JavaScript in an iOS/Android app. One method is to leverage the system browser component, UIWebView on iOS and WebView on Android; the other way is to compile and integrate a full-featured JavaScript engine.

Using the system component is easy to implement but it’s inflexible and inefficient. WebView provides addJavascriptInterface to inject Java classes into JavaScript context. But it only supports primitive data types which brings restrictions to API design; it’s also unstable and crashes on Android simulator 2.3 and some real devices according to issue #12987. Things are worse on iOS, UIWebView doesn’t have public APIs to support direct interaction from JavaScript to Objective-C (You have to use private APIs to achieve the same functionality of addJavascriptInterface).

PhoneGap is the most famous project that is built upon UIWebView and WebView. Developers are forced to use callbacks to retrieve return values from its JavaScript APIs, which is complex and inefficient especially for games.

Some earlier versions of ngCore also relied on UIWebView in order to support iOS. This mechanism has been replaced because of the awful performance.

In order to get better performance, flexibility and compatibility, it becomes popular by embedding a full featured JavaScript engine in native apps.

Choices of JavaScript Engines

As far as I know, four JavaScript engines could be built and ran on iOS or Android: JavaScriptCore, SpiderMonkey, V8 and Rhino. The table below lists their compatibilities on iOS and Android.

iOS Android
JavaScriptCore Interpreter only Interpreter and JIT
SpiderMonkey Interpreter only Interpreter and JIT
V8 JIT only for jailbroken devices JIT
Rhino Unsupported Interpreter

When I was searching for the right JavaScript engine for OpenAphid-Engine, my evaluation focused on the following metrics:

  • Compatibility. The engine should support both iOS and Android, and work on both simulators and devices, which requires it support both ARM and x86.

  • Stability. It should stably work on both platforms and supported CPU architectures.

  • Extensibility. Extending the engine to add native features should be easy. For example, OpenAphid-Engine required a bridge layer to access OpenGL ES from JavaScript.

  • Performance. It’s boiled down to two primary factors: fast JavaScript evaluation, and efficient binding mechanism with low overhead. OpenAphid-Engine may trigger hundreds of OpenGL ES calls from JavaScript to render a single frame. The rendering would be slow if the overhead is much more significant than normal JavaScript routines.

  • Small footprint. The memory footprint and binary size of the executable file should be small.

Rhino and V8 were out first since they don’t support iOS. I really wanted to build OpenAphid-Engine with V8, which showed great performance and elegant code structure during my preliminary experiment on Android. But I got disappointed due to the fact that V8 only employed a JIT mode while iOS doesn’t allow JIT unless on a jailbroken device. Please refer to issue #1312 if you need more technical details.

I debated a lot between JavaScriptCore and SpiderMonkey. After successfully built them on iOS and Android, I applied benchmarks and experiments to find the better one.

SpiderMonkey is available under a more friendly license, but it lost in nearly all of my measurements compared to JavaScriptCore. It generated larger binary file size (about 1.3MB larger for ARMv7); JavaScript evaluation was slower and the performance overhead of bridging JavaScript and C++ was also more significant. One more reason that pushed me away was that my build of SpiderMonkey randomly crashed on iOS simulator.

The performance of a JavaScript engine can be affected by many factors, like the version of build toolchains, the version of engines, and the OS types etc. The chart below lists the running times of several micro-benchmarks with different builds of engines on an iPod Touch 4. Please check out the Google Doc if you’re interested at the precise running times.

benchmark

  • JavaScriptCore is the clear winner by a large margin.

  • I failed to find my build of SpiderMonkey, so I used three other custom builds from Cocos2d-iPhone-2.1-beta4, Cocos2d-x-2.1-beta3 and iMonkey.

  • All test apps were built with LLVM 4.1 in release mode; all engines were running in interpreter mode restricted by iOS.

  • Explanations of some benchmarks:

    • 1m-js_loop runs an empty loop for one million times.

    • 1m-native_function invokes an injected native function for 1M times while the native function simply returns undefined.

    • 1m-js_function is similar to the one above except the function is written in JavaScript.

    • fib(30) calculates Fibonacci(30) in a recursive manner.

    • sudoku-5 solves five Sudoku problems with the algorithm from this project.

  • 1m-native_function for JavaScriptCore was implemented by its portable C APIs, which is not the most efficient way to inject native functions.

  • SpiderMonkey is fast on desktop with its advanced method tracing JIT. But it’s a whole different story on iOS devices.

  • The build from iMonkey was faster than other SpiderMonkey builds in most benchmarks.

  • It’s definitely possible to get better performance from SpiderMonkey on iOS. ngCore 1.10 for iOS managed to embed a custom build, which outperformed other SpiderMonkey variants.

Adventure with JavaScriptCore

My study proceeded further after I settled down with JavaScriptCore:

  1. The running time of 1m-native_function was over six times longer than 1m-js_function and 1m-Math.abs(0) on JavaScriptCore. I also observed the similar performance issue on accessing properties of injected native objects.

  2. The C APIs had a clean design but was lack of flexible memory management APIs. It seemed difficult to resolve issues caused by circular references without deeper cooperation with the internal garbage collector.

  3. There were many release versions of JavaScriptCore available. The best one should be fast and compact for OpenAphid-Engine.

I abandoned the original plan of using the C APIs in order to solve problem 1 and 2. The version of JSC from iOS 4.3.3 was used, as it’s faster than the version from iOS 5 in interpreter mode with a smaller binary executable file.

Engines Used in Other Products

During the development of OpenAphid-Engine, I always kept my eyes on other products. The table below summarizes the JavaScript engines they are using underneath.

iOS Android
ngCore 1.6 and above UIWebView V8
ngCore 1.7 and later SpiderMonkey V8
Titanium JavaScriptCore V8 or Rhino
PhoneGap UIWebView WebView
Cocos2D-x JavaScript SpiderMonkey SpiderMonkey
CocoonJS JavaScriptCore JavaScriptCore
Ejecta JavaScriptCore Unsupported
directCanvas JavaScriptCore No clue

Next Story in Series

I will post my opinions on comparing ngCore to OpenAphid-Engine. Stay tuned!

Comments