Wednesday 28 March 2012

Monkey Tip - Be Careful Around Boxes

No, this isn't a warehouse safety message about lifting with your legs. Monkey features auto-boxing/unboxing between classes and primitive types. Coders with backgrounds in other OO languages like Java will probably be familiar with the concept but I imagine there are plenty of Monkey users who aren't, so it's probably worth explaining it a little.

Boxes? Huh?


The term "box" means a class that contains, or wraps, a primitive value type. The standard examples in Monkey are the IntObject, FloatObject, BoolObject and StringObject classes that you'll find in the boxes file in the standard monkey module. These classes are generally used so that primitives can be treated as object instances and vice versa. The auto part of the boxing operations allows you to write code like this:

Local i:IntObject = 1
i += 1

Why would you want to do this? Well in Java this sort of thing is often employed so that primitives can be used with collections classes that require object references. Monkey's standard collection classes take a different route and don't use boxes but it's likely that you'll run into libraries with collections classes that do (Diddy, for example). Other than that it's sometimes useful to be able to declare that a class has a primitive representation or to create "smart" primitives. My JSON library makes use of boxing so that you can easily retrieve primitive values in the JSON structures. For example:

Local x:Float = jsonObject.GetItem("x")
Local y:Float = jsonObject.GetItem("y")
Local isActive:Bool = jsonObject.GetItem("isActive")
Local name:String = jsonObject.GetItem("name")

So, Monkey's automatic boxing and unboxing deals with the chore of converting between the representations and, in theory, the code is easier to write and read. I say "in theory" because the syntax is hiding what is actually going on. It's an example of what some refer to as syntactic sugar or, less flatteringly, "magic". It pays to understand what's actually happening, especially if you intend to use the feature yourself, as the reality can trip you up.

Pulling Back the Curtain


Let's demystify this language magic a little bit. What actually happens in the first example above? Here are those two lines of code compiled to C#:

bb_boxes_IntObject t_i=((new bb_boxes_IntObject()).g_IntObject_new(1));
t_i=((new bb_boxes_IntObject()).g_IntObject_new((t_i.m_ToInt())+1));

The first line is relatively straightforward -- the assignment is converted to a constructor for an IntObject instance.

The second line is a bit more interesting. It shows that the simple += operation is converted into a call to the ToInt method on our previously constructed IntObject, an addition and then another constructor call to create a new IntObject instance with the summed value.

And that's actually all there is to boxing and unboxing. It's simply a matter of implementing certain method signatures for construction with, and returning of, the boxed value. In general form they are:

Method New( value:{PrimitiveType} )
Method To{PrimitiveType}:{PrimitiveType}()

Where {PrimitiveType} is replaced with Int, Float, Bool or String, e.g. Method ToInt:Int(). All you have to do is implement those methods on any class and, presto, you've got an auto-boxing class. You don't have to implement all of them, for instance it is very common to only implement ToString, which provides a useful shortcut to creating debug prints like:

Print "My class info is: " + myBox

So what are the problems?

Performance


 The first one may have been obvious from the simple IntObject example above. Boxing and unboxing is much more expensive than using the primitive types. Assignment means construction of a new instance and that can lead to high garbage collection costs if you use boxed types without care. If you have need to process boxed values somewhere performance sensitive then you should unbox them, work with the primitive values and then assign them back to the boxed versions.

Compatibility


 Be aware that auto-boxing is a Monkey feature that didn't arrive fully formed. In particular, the demo version of Monkey is currently at 45c and some parts aren't working in this version, e.g. the "+=" operator from above. If you're releasing code for others to use then this may mean that you have to field bug reports/complaints.

True may no Longer be True and False may be a Null Pointer Exception


When you implement the box methods you are potentially changing how Monkey will interpret operations on objects of that class. A good example is the use of the following as a test for an uninitialised reference:

If myInstance
    DoAThing()
End

In the normal way of things, that code would only execute DoAThing() if myInstance had been initialised but not if it hadn't or if it was set to Null. However, if myInstance's class implements the ToBool method then Monkey will invoke it and attempt to unbox to test the boolean value. This will, of course, crash with a null reference exception of some kind if myInstance is uninitialised.

A similar problem can occur when doing type-testing via the casting mechanism:

If MyClass(myInstance)
    DoAThing()
End

Again, if MyClass declares ToBool then this will result in an attempt to unbox after the cast. If myInstance isn't an instance of MyClass then you'll get a null reference error. The answer to both of theses is to explicitly test against Null, e.g.

If MyClass(myInstance) <> Null

That way Monkey doesn't attempt to unbox to a Bool. In fact, I'd recommend getting into the habit of typing out the explicit test as this is a potential cause of very difficult to find bugs.

One May Not Equal One


Another gotcha to be aware of around boxed primitives and logic is that the "=" comparison won't cause values to be unboxed.

Local a:IntObject = 1
Local b:IntObject = 1

If a=b
    Print "This is never printed (unless Monkey starts to do interning and then it might be!)"
End

A is not equal to b in the example above because the comparison tests whether a and b are references to the same object and not whether they box the same value. To check the values you would have to unbox at least one of them yourself to make the comparison based on the integer value. Stylistically, I'd suggest explicitly unboxing both: "If a.ToInt() = b.ToInt()"

I'll end part one of this post here. Yes, there's more.

Tuesday 27 March 2012

Monkey Tip: Relative Target Performance

I've previously posted about how Android (at least on my phone) is a platform that presents performance challenges when doing cross-platform development with Monkey*. This is true, but I wanted to post something more general about cross-platform performance. So, here it is.

 *This isn't specific to Monkey, of course, but the comfortable abstraction that Monkey provides means that it's more tempting to believe that all the platforms are the same than if you were writing your own cross-platform libraries.

Box2D


First, let's look at an example of an Android performance gap. Here are the timings for the Box2D domino pyramid performance test. They show the results for a run of 300 updates, ignoring all rendering and other per frame costs.

HTML5(Chrome 17)
total: 3334, low: 8, high: 18, avg: 11.07641196013289

XNA(Windows)
total: 764, low: 0, high: 16, avg: 2.538206

Android(ZTE Blade running 2.3.7)
total: 43988, low: 109, high: 295, avg: 146.13954

That's right. No joke. The phone is over 10x as slow as Javascript and nearly 60x slower than an XNA build running on my modest laptop (2GHz T4200, Intel GM45 chipset, if you feel the need to know). You really need to be careful about making any judgements about how fast your app will be on a phone if you're taking advantage of the rapid turnaround on another target.

However, that Box2D test is all about crunching numbers, constructing objects and other CPU/VM-heavy activities. For many games the rendering performance impact is going to outweigh the computational costs and so it's useful to have an idea of how things relate there.

Rendering Comparison


Here's an example for my current project. I was interested in being able to "fog out" objects to simulate distance (or, hey, actual fog). The simplest mechanism I could think of for doing this was to render a rectangle with a suitable alpha over the top of something that I wanted to be dimmed.

Before I went with that as a technique I wanted to understand what the costs might be cross the platforms, so I whipped up a test scenario and got out my measuring stick/timing class. The test was simply to render a 400x400 rectangle with a 0.1 alpha 1000 times, repeat that enough to get a decent average and, as far as possible, to separate the actual costs of the rectangle rendering.

Here are the average results for the 1000 rectangles:

Flash Player 11 (in Chrome): 1360ms
HTML5 (IE9): 425ms
HTML5 (Firefox 12): 240ms
HTML5 (Chrome 17): 215ms

XNA (Windows): 250ms
GLFW (Windows): 260ms

Android (ZTE Blade): 794ms

On the web side that result for Flash was a bit of a shocker. I'm not sure if it represents the true poor performance of Flash in this case or it's a problem with Monkey's translation. The FF and Chrome JS/Canvas engines are reasonably close together, but IE seems to be off on its own somewhere more relaxed.

The XNA and GLFW figures give a view of a "proper" desktop target. Oddly, they're marginally slower than the fastest canvas targets. I honestly don't know why that would be the case. I guess it's possible that the canvas rendering is as fast as the windowed XNA and OGL paths and maybe v-synching explains the rest, but it doesn't really add up.

As could be expected, my little Android phone is some way behind the beefier platforms but it's only 3-4x slower, which is far, far closer than in the Box2D update comparison. With these numbers I can be reasonably confident that I'm not digging a hole for myself by using a few fog rectangles around the place.

The other thing I did was to quickly test some variations of numbers and size of rectangle to see if these times could be generalised to fill-rates. I couldn't raise the enthusiasm to draw up some graphs but perhaps you'll take my word for it that they pretty much are. Render half as many rectangles? Half the time. Render twice as many at quarter of the area? Half the time. So I can, as a rule of thumb, figure that I can fill a screen's worth of my 800x480 phone with alpha rectangles in under 2ms. That's a handy thing to be able to bear in mind.

As We're Here, What About Bitmaps?


While I had the code all set up I thought I might as well check to see what the relative cost of rendering a bitmap with embedded alpha was (like a smoke texture). So here's the same test but drawing a 400x400 semi-transparent image:

Flash Player 11 (in Chrome): 475ms
HTML5 (IE9): 2970ms
HTML5 (Firefox 12): 625ms
HTML5 (Chrome 17): 550ms

XNA (Windows): 273ms
GLFW (Windows): 275ms

Android (ZTE Blade): 1590ms

The fact that Flash is considerably faster at rendering this test makes me more suspicious that the rectangle rendering is doing something odd. Worth investigating some time.

Both FF and Chrome are about 2.5 times slower at rendering bitmaps, but look at IE! That's really slow. So slow in fact that it declared the page unresponsive a few times. The world of JS engines and Canvas implementations continues to offer excitement, mystery and intriguing inconsistency to the unwary dev (and the wary ones, in fact).

The XNA and GLFW numbers barely change from the rectangle rendering. The Android time has doubled, but it's in line with the hit on HTML5, so we can still have a rough feel for what we can get away with if we're mostly building to HTML5 on Chrome or FF.

The Conclusion Bit


What I take away from this is that it pays to test the impact of a specific technique across the platforms you care about before you tie yourself to it. You can't make assumptions that relative performance between targets in one area will hold in another. In fact you can't even trust that the same target will do so in the case of HTML5. Measure twice, cut once and all that.

Why Monkey Isn't A Terrible Choice

Psst... this is a big post. I get that you're a busy person avoiding lots of important tasks by reading this blog post. If you're desperate to get on with avoiding those tasks elsewhere I'd suggest at least reading the last entry.


Monkey Logo

Monkey is a cross-platform language that I've been using for games coding for around a year now. The general idea is that you write code in Monkey and then that code is translated to a "native" language for a specific platform, so Objective C for iOS, Java for Android, AS3 for Flash, C# for XNA etc. I picked up the demo not long after release and bought a full license a little while after. In the time I've been using it I've ported two physics libraries (Fling and Box2D), released my own JSON library and I'm nearing completion on my first game based on my own framework.

For a while now I've been considering posting about Monkey (and its general development environment) but I've held off doing so. The reason being that I am, by nature, fairly critical.
I'm not as bad as this guy. He sucks at being critical.
I like to think that I'm at least as hard on myself as others but even if I am it's not necessarily much comfort to those others. Monkey has been going though some growing pains and, while I'd always give an honest opinion if asked, I didn't want to broadcast negatives about an immature product. Now though I feel that I've added a fair amount to the Monkey community to offset any negative views. Also, it has been a year and Monkey should be able to stand some criticism.

However, before I start picking holes in the product, I want to lay some positive foundations. So that's what this post is - a set of reasons to give Monkey a shot that I can point to in the future when people accuse me of being negative and spanking the Monkey, so to speak. On with the Monkey love:
Hot.


Reason 1 - Mom 'n' Pop Service


Mark Sibly at BRL

Monkey is developed by Blitz Research Ltd (BRL). It's a very small company with pretty open access. Got a problem with Monkey? You can email Mark Sibly, the programmer and el queso grande of BRL. Something wrong with the provided Monk editor? The guy who writes the editor is on the forums. If you like dealing with small companies rather than large corporations or amorphous open-source organisations then Monkey gives you that level of personal interaction.

Reason 2 - It's The New Thing, Same As The Old Thing


In a good way.

If you're a programmer who is seriously intending to create games then attaching yourself to a new technology stack is fraught with dangers. The investment in learning that stack and the time/effort value that will exist in the code you write to run on that stack is considerable. In a worst case scenario you can end up throwing away weeks or months of effort. I will admit that I was highly dubious about Monkey but there are some facts that  persuaded me that it was at least worth a look:

BRL has a history of creating high-level game-friendly programming languages going back to the days of the Commodore Amiga. I'm old enough to have briefly used Blitz Basic back then in some early dabblings with graphics coding. So, the company has past form in terms of creating and maintaining these products and they've done it successfully enough to still be doing it.

The Monkey language, while bringing a bunch of new stuff to the table, is still in that same family of BASIC-ish languages. The language offers familiarity to many people and a relatively readable syntax to beginner programmers - unless you really hate BASIC, that is. I'm on the fence as to whether I hate or loathe BASICs, but it's not an unknown landscape to me and that has value when faced with so many other unknowns.

Reason 3 - It's Cheap, But Not Too Cheap


Monkey is currently 120USD for the full license. That's a one-off charge with no annual support fees or royalty cuts. Price-wise it undercuts the commercial competition by some margin (Although it also provides fewer features. I won't claim that it's a direct replacement for Unity, for example.)

On the other hand, 120USD isn't peanuts. I'm a big fan of open-source and HaxeNME is coming along nicely and that does mostly the same and is free. Surely free is better than 120 dollars? In the wallet, sure, but when there's something to be said about the value of a product that you know someone is invested in for their livelihood. Sometimes open-source projects are amazingly high quality and the teams are responsive to their users' needs and sometimes they really aren't. I don't know which one HaxeNME is or will be but you might consider that 120 dollars is a fair price to be able to at least imagine that you are understood to be a customer.

Reason 4 - It's Not a Black Box


Some cross-platform solutions are very jealous of attempts to peek at the workings or they force you to publish via server processes that they control. Monkey does none of this. If you want to get into the workings of things then you can.

Monkey coding is this manly.
The language itself is public domain and source for everything is openly provided in the distribution. The translation process spits out readable(ish) code in the target's native language and even creates IDE project files for many of them.

This means that, if you're willing and able to, you can operate directly on the native code using native tools (e.g. profilers and debuggers). You can even change the Monkey language itself if you want and are willing to carry the burden of maintaining the branch.

Reason 5 - It's Not Horrifically Slow


...considering what it is

Performance was a concern for me. Mobile devices are amazingly powerful these days but you're still dealing with some compromises and an abstracted rendering layer and translated language are compromises on top of those.

I've been pleasantly surprised though. There's no doubt that you would get more performance by working natively but Monkey's overhead isn't going to stop you from creating attractive 2D games. At some point I'll see about making some comparisons if I can find simple game-y demos to reproduce but for now I'll just offer mostly anecdotal reassurance and my one data point about my Box2D port to Monkey only being half as fast as libGDX (honestly, that's not terrible!).

Reason 6 - It's Not a Toy


But it's fun
I'll be honest. In the early days of using Monkey I really thought it was a dead-end for me. I pretty much immediately started writing framework code and converting the Physaxe library and it was obvious that nothing substantial had been written in the language during beta testing. Blocking issues with the compilation tool-chain flaking out were a regular occurrence and after working in C/C++, Python, Ruby, Java and C# the language seemed inconsistent and under-powered in a design sense.

I think I turned a corner on my plans to jump to Unity, Corona or Haxe when Monkey implemented interfaces. Initially, when asked about adding them the response was bluntly that they wouldn't happen until there was a larger rewrite and that was "at least a year" away. That was no good to me and I started looking around for options. Then, for whatever reason, interfaces were added. They were somewhat broken at first but it rescued my sense that the language could support what I wanted to do (without excessive frustration). Since then, as I mentioned at the start of the post, I've written thousands of lines of fairly structured code and nearly all of them work.

Monkey isn't a power-house of language features, "enterprise-ready" or a startlingly elegant example of language design but it is capable of supporting the creation of reasonably complex software without it collapsing into a ball of mud. It manages to avoid the trap of attempting to be easy to use at a beginner level at the expense of being able to support growth into a more expert programmer and software designer.

Reason Everything - Fear Not The Future


Because the future will be this awesome.
Monkey offers out of the box (some assembly required) push button compilation to HTML5, Flash, iOS, Android, WP7(XNA), XBox(XNA),Windows(XNA/GLFW) and MacOS(GLFW). I won't claim that it's perfect or without complications but, for the most part, you can write code once and then just compile to the platform you're interested in.

There's no doubt that this is the actual reason I picked it up in the first place and the reason I stuck with it. The other reasons listed above all together aren't as important as this one. I wanted to try creating and selling a game but I was unsure about which platforms to target. I was especially concerned with the possible need to push out a Web version as well as a mobile version and to be able to hit both iOS and Android. As a solo coder, the thought of having to create a cross-platform game in C/C++ and maybe port that to Flash/HTML5 to create a promotional web version was daunting. It would have also front-loaded a bunch of learning and costs (if I wanted to write for Android and iOS then I'd need to buy an Android device, an iPhone or iPod Touch and a Mac of some sort from the start). By going with Monkey I could push nearly all of that back and just get on with writing the game.

It gets better though. Monkey doesn't solve all of the cross-platform issues, just most of them, but for the ones it doesn't solve it provides pretty easy routes for you to solve them yourself. Writing native extensions is quite simple. Monkey also generates working projects for the platforms so you can, if you want, just open up the translated code in XCode , Eclipse or VS and add platform-specific features.

But there's more. We're in an era of platform proliferation and shifting ground. Windows 8 is around the corner with Metro-based tablets and Microsoft's own concepts of an app store. Will MS relent and allow XNA? Will HTML5 be a realistically performant target? Panic? Probably no need if you're using Monkey. When the smoke clears on the tech side (and if Metro doesn't flop) then BRL or someone else will put together a target for it and you'll recompile. No reason to panic at all.

Always carry a towel. Coding in Monkey helps too.
But what if they don't? Or what if BRL goes bust or Mr Sibly gets bored or something? Still no reason to panic. As I mentioned above, the Monkey language is actually public domain. Only the graphics, sound and input abstraction library -- mojo -- is under a commercial license and the source code for everything comes with the installation. As such it seems highly likely that a community effort could quickly be put in place to keep the language and tools going.

Even if, for the sake of argument, you decide that you absolutely must jump ship then you still have the ability to compile your Monkey code to one of the targets and use that as a base for your next step. The translated code isn't pretty, but it's munged in a predictable way and I doubt it would take more than a week or so over a project to tidy it into something perfectly reasonable.

Ultimately that's what I feel Monkey gives me: options and the ability to delay decisions with minimal cost. There's no guarantee that Monkey is the perfect choice for any given project but it is certainly one of the least binding imperfect choices you can make.

Monday 26 March 2012

Monkey vs. libGDX Box2D Performance On Android

Note: The figures in this post are now out-of-date. I posted an update here.

The game I'm working on uses Box2D, or rather a port of Box2DFlash  to Monkey that I did. My game's use of Box2D isn't all that stressful but I have been curious as to exactly what sort of performance hit is entailed in using my port. In particular I've been curious about the cost on Android as that's the platform where things are tightest.

The problem with comparing these things is that it's hard to remove all uninteresting variables such as differences in the things being modelled or the costs of rendering and such. Today my curiosity got the better of me though and I downloaded the libGDX code, which compiles for Android and includes a version of Box2D accessed via JNI.

In order to compare like with like, I ported the most intensive demo from the Monkey test suite, which is a domino stack. I also fixed the update in libGDX's tests (which was oddly set to use frame delta-time, meaning that all the collisions failed if the frame rate dropped) and made the update iterations match the Monkey test standard. Finally I changed the timing code to only care about the update and to give values for the highest, lowest and average update times.

Here's the result (on my ZTE Blade running Android 2.3.7):



So we're roughly looking at a range of 65-100ms with an average of 75 ms. How does that compare to the Monkey port? The libGDX version is about twice as fast on average with the Monkey port running about 140-150ms per update. Monkey's variability is worse though and the worst case hits 300ms, so libGDX is even better in that regard.

Although that sounds like bad news, I'm less concerned now. The domino stack demo represents a scenario to be avoided. If your game relies on that sort of thing then today's mid-level mobile devices aren't really where you should be planning to release and certainly not without expecting to go native. I'm quite okay with half the speed in return for the ease of cross-platform development for my own needs.

That's not to say I'm happy to leave it as is. I doubt there's much more performance to wring out of micro-optimisations to the port but I certainly plan to revisit potential ways to remove the GC spikes. Another possibility is porting more recent code from the main Box2D implementation, but I've no idea if there have been any relevant performance improvements there.

There is one other potential source of a performance boost and that's if a native Android target is added to Monkey. I can imagine there are some headaches there with device compatibility and the like but for computationally intensive stuff like Box2D it could make a big difference.