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¶m2=value2¶m3=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 Everything mentioned in this post has been officially released to Haxelib in Lime 8.1 and OpenFL 9.3!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!
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!