OpenFL devlog: updateAfterEvent(), FileReference improvements, option to use Terser minifier

I thought I’d share another update about the new features that I’ve been working on recently for the next versions of OpenFL and Lime. In today’s post, I’m going to talk about implementing updateAfterEvent(), which makes it possible to influence OpenFL’s rendering frequency, improvements with downloading and uploading files with openfl.net.FileReference, and adding the Terser minifier to Lime as an option for the html5 target.

If you’re not familiar, OpenFL is an implementation of the APIs available in Adobe Flash Player (and Adobe AIR) using the Haxe programming language. Projects built with Haxe can be cross-compiled to JavaScript, C++, and many other targets — making it available on the web, desktop apps, mobile apps, and more. No browser plugins required, and fully native C++ performance.

Implementing updateAfterEvent()

In Flash, the MouseEvent, TouchEvent, KeyboardEvent, and TimerEvent classes all have a method named updateAfterEvent(). According to the API reference for Adobe AIR, this is what the method does:

MouseEvent.updateAfterEvent()
Instructs Flash Player or Adobe AIR to render after processing of this event completes, if the display list has been modified.

That description may not be clear enough, so let me expand a bit. Calling updateAfterEvent() inside certain event listeners asks Flash to render more often than the requested frame rate would normally allow. Basically, this method allows developers to set the frame rate very low to improve idle performance (12 FPS was common back in the day, and it might have even been the default in the Flash authoring tool, but you could potentially go as low as 1 FPS). However, when the user is interacting with mouse/keyboard/touch, you can call updateAfterEvent() to effectively increase the frame rate temporarily so that user interaction feels very smooth.

Implementing updateAfterEvent() in OpenFL mostly involved some changes to the openfl.display.Stage class. The stage relies on lime.ui.Window to manage the requested frame rate. Every time that the Window is ready to render a new frame, it will dispatch its onRender event. The stage listens for this event, and that kicks off not just rendering all of the display objects, but also the dispatching of its own events, like Event.ENTER_FRAME, Event.FRAME_CONSTRUCTED, and Event.EXIT_FRAME that are used heavily by developers for animations and things.

OpenFL’s handling of mouse/touch/keyboard events also happens within the Stage object, so forcing a render outside of the normal frame rate is relatively convenient to implement since there is no need to communicate with other classes. However, within the Stage class, I did need to refactor a little to separate the rendering code from the code that dispatches of events like ENTER_FRAME because updateAfterEvent() should not cause MovieClip or other display objects to advance their frame playheads. It simply renders them in their existing frame again (if necessary).

I had to add one hack to make this work on native targets, like C++, Neko, and HashLink. On these targets, before Lime dispatches onRender on a window, Lime also calls window.__backend.render(). Then, after the dispatch, it calls window.__backend.contextFlip(). If these backend methods aren’t called, the state of OpenFL updates, but nothing visually changes on screen. These methods should be internal to Lime, but OpenFL currently has to break encapsulation to call them in order to force Lime to render outside of the normal frame rate. Ideally, Lime would have a public API that could force a render. This should be revisited in the future.

If you want to try out updateAfterEvent() with mouse events at a low frame rate, I should mention one thing that you’ll need to do first. OpenFL has an optimization (enabled by default) that throttles MouseEvent.MOUSE_MOVE events so that they don’t get dispatched more than once between two consecutive frames (technically, that’s not always true, but it’s close enough for our purposes). Since you’ll get MOUSE_MOVE only once between frames, updateAfterEvent() doesn’t add a lot of benefit because it can only trigger one extra render, at most. However, the person who implemented this MOUSE_MOVE optimization provided a define that will restore Flash’s original behavior that allowed multiple MOUSE_MOVE events between frames.

Add the following to your OpenFL project.xml file, and you’re good to go:

<haxedef name="openfl_always_dispatch_mouse_events"/>

OpenFL does not throttle TouchEvent or KeyboardEvent in the same way, so calling updateAfterEvent() on one of those classes will work automatically, without a special define.

FileReference improvements

The openfl.net.FileReference class is intended for downloading from and uploading files to a server. It conveniently provides native file system dialogs to choose where to save a downloaded file or to allow a user to select an existing file to upload.

The first improvement that I implemented was ensuring that FileReference could successfully download binary files with its download() method. Internally, FileReference uses a basic openfl.net.URLLoader. By default, the dataFormat property of URLLoader is set to URLLoaderDataFormat.TEXT, and the previous implementation did not change the dataFormat from that default value. If a binary file is loaded as a String instead of a ByteArray, data may be corrupted when attempting to save it to a file. On the other hand, a plain text file can be loaded as a ByteArray without any data loss. The fix to allow downloading binary files, while preserving the ability to download text files too, was as simple as using URLLoaderDataFormat.BINARY instead.

Until now, OpenFL’s implementation of FileReference did not provide an upload() method implementation at all. It simply threw an exception. Constructing the HTTP request to upload a file to a server is kind of complicated, so I understand why this didn’t exist yet, but my client needed it, so I needed to wade into the murky waters and figure out the multipart/form-data content type.

Most HTTP POST requests are submitted using the application/x-www-form-urlencoded content type. Parameters are sent separated by the & character with names and values separated by the = character, and certain characters are URL encoded, like this:

param1=value1&param2=value2&param3=value3%20with%20spaces

To submit a file in an HTTP POST request wouldn’t work well with that format, especially if it were binary instead of text, so that’s why the multipart/form-data content type is needed. It cleanly separates each parameter with more complex boundaries than the & character, and it doesn’t make any modifications to the values.

Normally, URLLoader generates the HTTP protocol request on its own. However, when using multipart/form-data, it’s necessary to do part of it manually. I ended up creating a ByteArray and writing most of the request with multiple calls to writeUTFBytes(), writing the file data with writeBytes(), and passed the resulting ByteArray to the data property of the URLLoader.

If the original URLRequest contains URLVariables, these also need to be taken into consideration. If the request’s method is set to URLRequestMethod.GET, the variables can be appended to the URL’s search query. However, if the method is URLRequestMethod.POST instead (which is most common for file uploads), the variables need to be inserted into the ByteArray that gets generated above.

Optionally using Terser to minify JS

By default Lime uses Closure Compiler from Google to minify JavaScript for the html5 target when using either the -final or -minify command line flags. Closure Compiler often generates smaller minified JavaScript than most other minifiers, so it makes good sense to use Closure Compiler as the default for Lime and OpenFL. However, there are other popular minifiers out there, and for some JavaScript code, sometimes those other minifiers are the better choice, so I thought it would be nice if Lime could provide more options.

In fact, Lime has long had the ability to use YUI Compressor instead of Closure Compiler, by adding the -yui command line flag. YUI Compressor was once one of the leading minifiers for JavaScript, but it eventually stopped being as useful because it stopped being updated with the latest JavaScript syntax, so it may fail when it sees code that it doesn’t understand. In my experience, using -yui with OpenFL is impossible because OpenFL includes some JavaScript libraries that YUI Compressor can’t deal with properly. I’m just mentioning it to show that Lime already supports multiple minifiers, so why not one more?

One of the most popular minifiers in the JavaScript ecosystem today is called Terser. Terser runs on Node.js and is included with Webpack by default, so a lot of JavaScript projects use Terser, even if they don’t realize it. Considering its popularity, in my mind, Terser was an obvious choice to include as an alternative to the default Closure Compiler.

Lime already bundled Node.js binaries to launch an HTTP server when you run the lime test html5 command. However, the included Node.js version had gotten pretty far out of date, so I updated to Node.js v18, which is the current recommended LTS release at this time. Then, I installed Terser and all of its dependencies locally, and I copied the necessary files into Lime’s repository. Inside Lime’s command line tools, I implemented a new -terser command line flag to tell Lime to use Terser instead of Closure Compiler when minifying for the html5 target. To build, run lime build html5 -minify -terser, and you’re good to go (and it also works with test instead of build)

I also added a new -npx command line flag. This tells Lime’s command line tools to use the npx tool from Node.js to run the latest versions of the google-closure-compiler or terser npm modules instead of the ones bundled with Lime. This should allow developers to take advantage of the latest minifier features (or possibly work around any bugs) without waiting for Lime to update its bundled dependencies and release an update.

Where can I find the code?

If you want to try out the new updateAfterEvent() feature, the improved FileReference class, or the Terser minifier, you’ll need to check out Lime’s 8.1.0-Dev branch and OpenFL’s 9.3.0-Dev branch on Github, or you can download both the lime-haxelib artifact from a successful Github Actions Lime 8.1.0-Dev nightly build and the openfl-haxelib artifact from a successful Github Actions OpenFL 9.3.0-Dev nightly build. If you check out Lime’s source code from Github, you’ll need to rebuild the .ndll binary files and Lime tools, so you may find it easier to go with the pre-built artifacts. Of course, nightly builds are not necessarily ready for release to Haxelib, so use at your own risk in production. You may encounter some bugs, but we’d love any feedback you can give. Thanks, and happy coding! Everything mentioned in this post has been officially released to Haxelib in Lime 8.1 and OpenFL 9.3!

If you love these devlogs, and my contributions to OpenFL and Lime in general, please consider a monthly donation as a token of appreciation on my GitHub Sponsors page. Thank you!

OpenFL devlog: NativeWindow, Lime’s Haxe and C++ bridge, and improved cleanup

Recently, as a member of OpenFL’s leadership team, I decided that I wanted to do more to keep the community up-to-date with what new features I’m working on for the next versions of OpenFL and Lime. In today’s post, I’m going to talk a bit about the upcoming implementation of the openfl.display.NativeWindow class, which is based on a core API introduced way back in Adobe AIR 1.0. The purpose of the NativeWindow class is to open a new window (with its own stage and display list) on desktop operating systems, like Windows, macOS, and Linux.

If you’re not familiar, OpenFL is an implementation of the APIs available in Adobe Flash Player (and Adobe AIR) using the Haxe programming language. Projects built with Haxe can be cross-compiled to JavaScript, C++, and many other targets — making it available on the web, desktop apps, mobile apps, and more. No browser plugins required, and fully native C++ apps.

Implementing NativeWindow

Lime, which provides OpenFL’s integration with the native operating system, has the ability to open multiple windows through its use of SDL. SDL is a C++ library that provides a cross-platform interface for things like graphics, sound, gamepads/joysticks, and more. It’s commonly used in games, but isn’t necessarily restricted to that category of program.

Lime’s Window class was actually already exposed in OpenFL, including creating a new stage for each window. However, one of my goals as a contributor is to ensure that developers adopting OpenFL can easily migrate from Flash or AIR. So I needed to wrap Lime’s API with a new class that offers the familiar interface of AIR’s NativeWindow. In the process, I discovered that I had to expose a few new APIs that Lime didn’t provide yet. And, I was able to find some ways to optimize and improve cleanup of a window and its stage (and the entire application… more on this below).

For the most part, implementing NativeWindow was pretty straightforward because SDL provides many similar APIs to what is available in AIR, and many of these were already exposed through Lime. However, not all of these SDL APIs were fully exposed yet, and there were important differences in behavior between Lime’s Window and AIR’s NativeWindow where Lime needed some tweaks to support both behaviors. For instance, by default, Lime’s Window immediately opened on creation, but AIR keeps a window hidden until the visible property is set to true, or the activate() method is called.

Lime had actually exposed a way to hide a window by default, but there wasn’t yet a property/method to reveal it later. So I needed to dive into Lime’s C++ code and expose a new window visible property to Haxe that could be changed at runtime. Similarly, AIR allows developers to optionally set minimum and maximum dimensions of a NativeWindow, and Lime hadn’t yet exposed SDL’s functions for that feature yet.

Bridging Haxe and C++ in Lime

Adding new C++ code to Lime that can be called from Haxe involves changes in several files.

  1. The .hx class that provides the public API. In the case of the new visible property, that’s Window.hx.
  2. Lime APIs are usually powered by a separate “backend” class for each target. In this case, Window.hx calls into NativeWindow.hx (to be clear, that’s lime._internal.backend.NativeWindow, which is different from openfl.display.NativeWindow).
  3. C++ backend classes call methods defined in the NativeCFFI.hx class. NativeCFFI tells Haxe how to find the C++ APIs exposed by the compiled .ndll binary (or .hdll binary for HashLink). It requires separate declarations for Neko, HashLink, and hxcpp, so it’s kind of a confusing file to work with because there’s a lot of duplication.
  4. The ExternalInterface.cpp file, which exposes the public C++ API for the .ndll and .hdll binaries. This file usually doesn’t contain the concrete implementation, but instead, is more of the glue that talks to other C++ classes that do the actual work.
  5. The .h and .cpp file where the actual C++ concrete implementation exists. In this case, that’s SDLWindow.h and SDLWindow.cpp.

Adding new Haxe APIs to Lime that are implemented in C++ isn’t actually very hard, but it is (admittedly) a complicated process because there are so many places that can be affected by a single API change.

Improved disposal/cleanup

As I was implementing OpenFL’s NativeWindow class, I took some time to consider memory management. Many OpenFL apps have one window only, including mobile and JS/web apps (yes, Lime and OpenFL still have a “window” concept when building for web browsers). With that in mind, there’s often only one Stage and display list too. However, with NativeWindow, each additional window creates a new Stage. When closing a window, it’s important to clean up that stage as well, to make sure that there aren’t any memory leaks, or worse, memory leaks plus extra code running without end — even if it isn’t needed anymore. (The first thing I checked was that Event.ENTER_FRAME wasn’t still firing out of control, but thankfully, that was already working properly.)

Ideally, when an OpenFL app exits, it should close all of its windows and remove any references to itself from static variables. Doing this manually isn’t necessary on desktop and mobile because the operating system will clean everything up automatically when the app exits. However, when targeting JS/web, if you were to manually “exit” an OpenFL application (such as by calling Lime’s System.exit() method), or if you were to manually call close() on the “window”, that doesn’t necessarily mean the browser has navigated somewhere else. The app could be embedded in a page with other content, and it may be important to be able to load a new instance of the same OpenFL app again, or even replace it with a separate OpenFL app (or something entirely different), inside the same parent HTML DOM element at a later time.

The easiest improvement was setting the static properties lime.app.Application.current and openfl.desktop.NativeApplication.nativeApplication both to null on exit. It wouldn’t be possible for the application instance to be garbage collected without this change. Similarly, a number of event listeners defined by the application are added to static dispatchers that expose notifications from C++. These should be removed when the application exits too, so that they don’t prevent garbage collection.

Gamepad.onConnect.remove(__onGamepadConnect);
Joystick.onConnect.remove(__onJoystickConnect);
Touch.onCancel.remove(onTouchCancel);
Touch.onStart.remove(onTouchStart);
Touch.onMove.remove(onTouchMove);
Touch.onEnd.remove(onTouchEnd);

On the JS/HTML side, “closing” the “window” should also clean up the HTML DOM. Lime creates a <canvas> element that either renders to WebGL or falls back to software rendering. It also adds some event listeners to the canvas for mouse and touch. When the window is closed, or the application exits, the canvas should be removed from the DOM, and any listeners added to DOM elements should be removed too. Again, we need things to be garbage collected, and everything returned as close to the original state as possible.

var element = parent.element;
if (element != null)
{
	if (canvas != null)
	{
		if (element != cast canvas)
		{
			element.removeChild(canvas);
		}
		canvas = null;
	}
}

After all of these changes, Lime and OpenFL feel like much better HTML/JavaScript citizens, and it opens up some new possibilities for more complex websites where OpenFL is one small part of a larger system.

Where can I find the code?

If you want to try out the new NativeWindow feature, or the improved application/window/stage disposal, you’ll need to check out both Lime’s 8.1.0-Dev branch and OpenFL’s 9.3.0-Dev branch on Github, or download both the lime-haxelib artifact from a successful Github Actions Lime 8.1.0-Dev nightly build and the openfl-haxelib artifact from a successful Github Actions OpenFL 9.3.0-Dev nightly build. Of course, this code is not yet ready for release to Haxelib, so use at your own risk in production. There may still be some bugs. Everything in this post has been officially released to Haxelib in Lime 8.1 and OpenFL 9.3!

If you love these devlogs, and my contributions to OpenFL and Lime in general, please consider a monthly donation as a token of appreciation on my GitHub Sponsors page. Thank you!

OpenFL Devlog: Weak event listeners

Update (January 2024): Unfortunately, weak event listeners have been disabled for the C++ target, due to the way that Haxe internally implements passing methods by reference. Haxe creates a wrapper object around the method, so the wrapper object would be stored weakly, instead of the original method. Weak event listeners remain available for the JavaScript target.

About a year ago, I joined the OpenFL leadership team, in recognition of my contributions to the project. Since then, I’ve been working hard to improve things by fixing bugs and filling in missing features from the Flash API. If you’re not familiar, OpenFL is an implementation of the APIs available in Adobe Flash Player (and Adobe AIR) using the Haxe programming language. Projects built with Haxe can be cross-compiled to JavaScript, C++, and many other targets — making it available on the web, desktop apps, mobile apps, and more.

Over the last year, I’ve become more and more familiar with the OpenFL codebase — including Lime, OpenFL’s lower-level sibling that bridges the high-level Haxe code in OpenFL with low-level C++ code that is needed to integrate with the underlying operating system. It had been nearly 20 years since I last seriously used C++… and it’s been surprisingly enjoyable picking it up again. Sort of. I can do without the manual memory management (give me a garbage collector any day), but it’s exciting that I am no longer limited to working with what is already exposed at a high-level in OpenFL. If I’m feeling like getting my hands dirty, I can expose new native APIs, and hardware capabilities, that will empower other developers using OpenFL.

This is the first post of (I hope) many, where I give a technical summary of the recent features that I’ve implemented in OpenFL and Lime. Ideally, if I have time, I’d like to include some interesting low-level tidbits about each feature that you won’t find in the documentation, and would otherwise need to read OpenFL’s code to learn. In most cases, I plan to write about features that are actively in development, available only on Github at the time of writing, and not yet released to Haxelib. So be warned. The exact implementation details are subject to change, and if you want to try something out, you’ll either need to learn how to download nightly builds of OpenFL and Lime from Github Actions, or you’ll need to clone the repo and build it yourself. Without further ado, let’s get started on devlog #1.

Background on strong and weak event listeners

When you add an event listener by calling addEventListener(), like this…

dispatcher.addEventListener(type, listenerFunction);

…you create a reference from the dispatcher object to the listener function. Most references work this way: If object A is not yet eligible to be garbage collected, and it holds a reference to object B, then object B is also not yet eligible to be garbage collected. Later, if the reference from object A to object B is removed, then object B may be garbage collected (as long as there are no other references to it elsewhere). In terms of event listeners, removing the reference from the dispatcher to the listener function is done by calling removeEventListener(), like this:

dispatcher.removeEventListener(type, listenerFunction);

That type of reference is called a strong reference. There’s another type of reference, which is less common, called a weak reference. If object A is not yet eligible to be garbage collected, but it holds a weak reference to object B (and there are no other strong references to object B), then object B may be garbage collected.

An event listener may be added as a weak reference using the 5th parameter of addEventListener(), as seen in the code below:

dispatcher.addEventListener(type, listenerFunction, useCapture, priority, useWeakReference);

Weak listeners help reduce memory leak bugs by allowing objects to be garbage collected when you forget to call removeEventListener(), and the dispatcher shouldn’t be garbage collected yet. This is most common when adding event listeners to OpenFL’s stage (which is where all display objects get added if you want to render them). Stage listeners are added frequently for mouse and touch events, so ensuring that those listeners are removed is critical to writing correct OpenFL code. If the stage holds a strong reference to an object, that object probably isn’t getting garbage collected any time soon. That’s a memory leak, and if it happens enough times, it really starts to add up. When you add your listener as a weak reference, though, you can feel safe knowing that the object will eventually get garbage collected, even if you forgot the removeEventListener() call.

Implementation of weak listeners

Up until somewhat recently, it wasn’t possible to implement weak event listeners in OpenFL for most available targets, so OpenFL ignored the useWeakReference parameter, and added all references strongly. Event listeners in JS couldn’t be weak, and there simply wasn’t an API to weakly reference an arbitrary object in JS at all. For a long time, this was one place where OpenFL simply couldn’t match the capabilities of Flash.

However, between 2020 and 2021, web browser vendors started shipping the new WeakRef type for JavaScript. It provides a simple API for creating a weak reference to an object, instead of the normal strong reference. Here’s an example in JavaScript:

let ref = new WeakRef(targetObject);
let maybeTarget = ref.deref();
if (maybeTarget) {
    // object hasn't been garbage collected
} else {
    // object no longer exists
}

According to CanIUse, WeakRef has reached 92% availability. It’s ready for prime-time. However, when using WeakRef in OpenFL’s EventDispatcher implementation, I made sure to check if it’s available, and fall back to strong references, if needed.

Similarly, Haxe provides a cpp.vm.WeakRef for C++ desktop/mobile native targets that works very similarly. If you call its get() method, and the result is null, that means the object has been garbage collected.

var ref = new cpp.vm.WeakRef(targetObject);
var maybeTarget = ref.get();
if (maybeTarget != null) {
    // object hasn't been garbage collected
} else {
    // object no longer exists
}

In OpenFL’s EventDispatcher class, each call to addEventListener() results in the creation of a Listener object, which stores data about each listener that is added, such as whether the listener should be called for the capture phase, and what the listener’s priority is, which affects the order in which listeners are called. Basically, it’s a data structure that looks kind of like this:

private class Listener {
    public var callback:(Dynamic)->Void;
    public var priority:Int;
    public var useCapture:Bool;
}

In order to store a listener function as a weak reference, that callback member variable won’t work because setting it creates a strong reference. Weak listeners need to be stored differently.

private class Listener {
    public var callback:(Dynamic)->Void;
    public var priority:Int;
    public var useCapture:Bool;

    public var useWeakReference:Bool;
    #if (js && html5)
    public var weakRefCallback:WeakRef;
    #elseif cpp
    public var weakRefCallback:cpp.vm.WeakRef<(Dynamic)->Void>;
    #end
}

Similar to the useCapture field, I added a useWeakReference boolean field. Then, I added a separate weakRefCallback field to store the listener using the appropriate weak reference type for the target (JS vs C++).

Now, when dispatchEvent() is called, the dispatcher can loop through the Listener objects and quickly check whether a particular listener was added weakly or strongly. If the listener was added weakly, it can check whether the listener was garbage collected or not.

if (listener.useWeakReference) {
    var weakCallback = listener.weakRefCallback.deref();
    if (weakCallback != null) {
        weakCallback(event);
    }
} else {
    listener.callback(event);
}

Now, you might wonder, what happens to the Listener objects once the function held by a WeakRef has been garbage collected? Obviously, they will stick around in memory, which could slow down dispatchEvent() if you added a ton of weak listeners and never removed them. Avoiding memory leaks by trading for slower performance isn’t good, so EventDispatcher needs a way to clean things up from time to time so that it stays fast.

In the code above that calls the listener functions, I skipped some of the details. The Listener and WeakRef are actually cleaned up right away when EventDispatcher detects that the function has been garbage collected. The real code looks much closer to this:

var weakCallback = listener.weakRefCallback.get();
if (weakCallback != null) {
    iterator.remove(listener);
} else {
    weakCallback(event);
}

Maybe in the future, it might make sense to detect garbage collected weak listeners in other methods of EventDispatcher (not only in the dispatchEvent() method), but I feel like this is a good start that adds very little overhead.

Where can I find the code?

If you want to try this feature out, you’ll need to check out OpenFL’s 9.3.0-Dev branch on Github, or download the openfl-haxelib artifact from a successful Github Actions 9.30-Dev nightly build. Of course, this code is not yet ready for release to Haxelib, so use at your own risk in production. There may still be some bugs. Everything in this post has been officially released to Haxelib in Lime 8.1 and OpenFL 9.3!

And that’s the end of OpenFL Devlog #1. Stay tuned for more posts about my work on OpenFL internals and some new features that I’ll be implementing in the near future.

If you love these devlogs, and my contributions to OpenFL and Lime in general, please consider a monthly donation as a token of appreciation on my GitHub Sponsors page. Thank you!