Today I'm officially releasing OpenFL-bitfive, an alternative backend to OpenFL-html5. Similar to standard backend, OpenFL-bitfive assists you at creating HTML5 games (apart of standard Flash, Windows, Mac, Linux, Android, iOS, [...] ones) with use of comfortable Flash-like OpenFL API. A difference is that while focusing only on certain feature set, OpenFL-bitfive reaches quite higher performance and compatibility in its niche, also making it possible to develop mobile HTML5 games using the framework. This post goes in-depth about it.
A bit of story
Last month I was contacted to develop a game of interesting kind. A small game, one could say. A day worth of work actually. With one important condition though, game must be fully playable on mobile phone' browser. Oh, and there's a deadline too. But that wouldn't be much of a trouble, right?
So there go I, pretty confident about positive outcome of this. Range of tools available is not large but satisfying. Since game was going to involve tile-based levels, GameMaker: Studio was chosen.
Few hours later a prototype with core mechanic is layed out and tested on mobile device. All seems to work well. Pleased with progress I leave to have dinner while phone is still lying on the table with game open. Upon coming back I notice an interesting thing - device is practically overheated, and game is running at 5 FPS instead of required 15-20. After another hour of investigation, issue is narrowed down to standard "render to texture" functionality doing something very uncommon, causing game performance to slowly degrade over time. So yes, another game, another bug...
Being the core component of game, substitution could not be done without awkward sacrifices. But sure, there's still some time left, maybe the bug will be fixed. Next day (July 10) I file the bug report. Example is attached, problem is described clearly, and it would look like it could get fixed fast... or so I thought. After a week of silence it becomes apparent that it probably isn't going to be fixed that fast. And so the project gets delayed, and I'm left with a thought, do I really have to wait for fixes every time?
Though what are the options? Code in many of software solutions is proprietary, and moderately to heavily obfuscated. Not something you may easily debug to aid process of bug fixing. On other hand there are Haxe frameworks like HaxePunk and HaxeFlixel, that would be a good replacement if not one thing - neither are good with mobile HTML5. HaxePunk waits for a multitude of OpenFL bug fixes to work correctly. Situation with HaxeFlixel is better, but not appealing - sounds don't quite work, and the "entry level" testing device is pausing every few seconds to collect newly arranged garbage.
It's not like OpenFL-html5 is poorly written. Pursuing the goal of fully supporting Flash API is no easy task. It is an achievement of making it go as far. But with more functions comes larger footprint (and bugs). And also that small question: does your application really need all the things included?
Ultimately, a Canvas-based HTML5 game would consist of a single <canvas> element that would only move around the page slightly to cope better with different window and screen sizes.
After a bit more time thinking about this, I decide to take the initiative and make "that one framework" that I need instead of waiting for a miracles. No strange practices, no throwing memory around, no sacrifices for the features one won't use... no bullthings, you could say.
And so, five weeks and some thousand lines of code later, I'm proud to present OpenFL-bitfive.
OpenFL-bitfive features primarily custom source code, and is built on a set of principles:
When less is more
Instead of attempting to support as much of Flash API as possible, OpenFL-bitfive focuses only on subset that is actually used in bitmap-driven games. Working with HTML5 games since 2011, I can tell that I do have a grip of what is right and what is wrong about implementing frameworks for these. When you add a Bitmap to stage, it has an actual Canvas component inside (the BitmapData). There is no redirection nor drawing overhead in this. Element transformations are processed via CSS3. In their vast majority, events that you catch are actual DOM events. Things are as simple as they need to be.
If you've ever dealt with trying to figure out what is going wrong in your DOM under OpenFL-html5, you may have noticed that this part doesn't go easy. This is partially due to fact that DOM tree doesn't really look... like a tree. More like a... well, I'm not even sure:
In OpenFL-html5, on other hand, DOM remains... well, itself. If project is compiled with Debug configuration, framework will even do you a favour of labelling every element present on stage, also allowing to retrieve Haxe-side objects via node field of elements:
We've all heard horror stories about garbage collectors. Some as well did see the dark side of these when developing for mobile platforms. What is still considered "pretty OK" on desktop, may as well turn application into semi-interactive slideshow in mobile web.
OpenFL-bitfive tries to keep both memory usage and memory allocation minimal, creating instances only when needed and using pooling where acceptable.
Fancy when appropriate
Flash and HTML5 differ a lot, and each has its own quirks. This makes the process of implementing Flash API in HTML5 a hard task. However there are parts that overlap between two, and permit better implementation for specific cases of usage. OpenFL-bitfive takes advantage of these, allowing code to work just about as fast as it would in "plain" HTML5 where possible.
This means, for example, that BitmapData functions will utilize globalAlpha and globalCompositionOperation to allow alpha-related operations to be processed without using ImageData, granting noticeable performance boost and permitting simpler code.
Comparing OpenFL backends is no easy task, but here are a couple of graphs:
As expected, with fewer functions comes smaller filesize. I plan to further improve results here by introducing a number of flags to permit excluding unused functionality from generated code completely.
I don't know if that's the right term, but let's call it that - this is the rate at which according otherwise empty (single DisplayObject) project emits memory (as observed via profiler). Not completely sure what makes up for difference here.
Perhaps, something about rendering. And copyPixels using ImageData in OpenFL-html5, it seems.
There's something about the current implementation that browsers seem to like. Might be rounding of drawing coordinates, which has shown to improve performance earlier, despite of lack of visual difference.
There's obviously more to comparisons of two. You can contribute to repository with these tests, if you feel like it.
- Backend components had to be debugged separately until almost the middle of development (~two weeks in), since amount of inner references was making it impossible to compile even a blank OpenFL project without implementing ~40 classes.
- As of moment of writing this post, original bug (from discovery of which development started; see above) remains unfixed. Given that supported feature set is similar, one could even say that I won some time (as opposed to waiting).
- A fair part of BitmapData source dates as early as last autumn, from time when I was attempting to contribute several improvements into main codebase, but failed for various reasons.
- Originally I was going to add "just about 20" classes that were relevant to desired project type. Currently there are 78 classes.
I plan to further work and improve this project to both provide better performance and wider API coverage. Perhaps it's also worth telling that developing something for whole month (without working on other projects) has, let's say, some negative monetary effects, so I will be also developing a number of HTML5 games to keep things afloat and publishing the progress on blog. Stay in touch!
Project is hosted on Github. Thus you can either clone the project (or, better, use haxelib git) or download ZIP with current version.
... and what do you think?