Following up from my post about using Haxe/OpenFL to save some of my old Flash games, I do feel like I have to talk about some frustrating points that came up during the progress.
I will preface this by saying that the problems Haxe has now are different than the problems it had when I was using it previously. Throughout this process, I only came across a bare few instances where switching targets led to suddenly broken code. The OpenFL runtime never totally crashed on me like it used to do. I didn’t have to fiddle with DCE flags to prevent my code from being erroneously stripped from the build. It’s obvious that Haxe has improved a lot over the past few years, and that’s great. I do plan on continuing to use it to port my other game projects, and I would recommend it to anyone else for the same purpose. I do believe in criticizing the things we love though, so let’s get into it.
Tooling has gotten worse
When I was working on Iridescence, my IDE of choice was Flashdevelop. Aside from Visual Studio, I’ve still never used anything quite as good. The code completion was incredibly good and it had great debugging support. Some of my most productive hours were spent in FD and I was disappointed (though not surprised) to find that the project has been largely abandoned. The current recommended environment uses Visual Studio Code, which I personally don’t care for that much.
The Haxe plugin for VSCode is often unable to navigate to symbols or suggest fields for completion. Renaming does not work in the slightest (attempting to rename a field will not change even the instance you’re currently editing). There are no refactoring actions available. Automatic module import is hit-or-miss. I could honestly go on and on.
Another point of friction is that the Haxe compiler (or maybe just the VSCode plugin) will frequently stop reporting errors after a certain point, and only return more once you’ve resolved everything currently available. It’s very common to finally clear out one batch of errors only to discover that there’s much more work remaining.
Worst of all, the debugging experience is incredibly flaky. When debugging an HTML5 build, for example, stepping through code is inhibited by literal lag; you may press F10 twice and only see the cursor move one line, but then realize that the actual browser has stepped twice and you’re out of sync. Javascript debugging is especially bad; hovering over an object in the editor will display the Javascript object itself, with all its implementation details laid bare — not the Haxe object that one would expect. Imagine if inspecting a class in the .NET debugger showed raw MISL instead of logical objects you’ve been working with!
The saving grace with regard to debugging is that Hashlink, a new VM for Haxe that was somewhat recently released, has excellent support for debugging. This does unfortunately lead into my next gripe though…
Platform inconsistency is real
At one point, while trying to debug my cutscene code in Humphrey’s Tiny Adventure, I switched to the Hashlink target and was floored when everything started working exactly as I expected! The culprit here turned out to be the fact that default field values are not consistent across platforms.
In as3, basic types are initialized with default values. Int and Uint start as 0; Number starts as NaN; Boolean starts as false; and everything else is null. I naively assumed that this would be the same in Haxe…and as it turned out I was right! …half the time.
Apparently, default values are undefined behavior. On Hashlink, all my fields were being initialized with “empty” values that corresponded to their semantics; that is to say, since a plain old Int can’t be null on this target, it would be initialized as 0. In Javascript, though, where there are no static types and anything can be null, my fields weren’t being initialized at all — and since null < 0
is perfectly legal Javascript, errors like this were being totally ignored.
Maybe at some point Haxe’s Javascript target will use WebAssembly or emit code to initialize fields in order to maintain consistency in this respect. As it is, it feels like programming a Javascript-producing-program rather than a program proper.
Reflection is very lacking
The last thing on my list is a bit niche, but it means the world to me. Reflection is C#’s single killer feature for me, and I can hardly imagine seriously using any language that doesn’t have something similar baked in. Haxe does have a sort of reflection in the sense that you can easily read/write class fields by name, but inspecting the class to retrieve a list of field names or their types is only supported through the @:rtti
macro, which, to be honest, is not sufficient — querying a type for its RTTI information will only yield back fields on the immediate class itself, without any of its inherited members.
See here for why this spells a problem for my entity loading code!
My old Flash workflow allowed me to automatically deserialize entities from XML elements in an Ogmo Editor level file, along with any string, numeric, or boolean properties. In Haxe, it’s impossible to achieve this completely automatically, since only the topmost class in a hierarchy will have RTTI information captured. I work around this by taking the base class’s fields and concatenating them, but this still isn’t enough to solve the problem completely — in cases where the superclass of some particular Entity class also contains fields we want to set, I have no choice but to parse the values manually. If I was able to traverse all the way up the inheritance chain and gather field definitions as I went, but (as far as I could tell) there’s no way to do this either.
I do like Haxe, quite a lot — but these three problems in particular led to hours of wasted time across the last week. In the future I’ll be a bit better at avoiding them, now that I know what to keep an eye out for.